ウィンドウやアプリを切り替えなくてよいようにする
プログラミングの効率を高めるためには、 できるだけウィンドウやアプリを切り替えないように作業環境を整えることが大切です。 ウィンドウやアプリを切り替えると、 それだけで手間がかかりますし、 集中力も削がれてしまいます。
課題のプログラムを作成する場合は、 まず課題のテキスト全文をソースコードのファイルにコメントの形で貼り付けましょう。 そうしておけば、 ソースコードのファイルだけを見てプログラミングに集中できます。
GCC でのコンパイル時は必ず -Wall オプションを付ける
C 言語のコンパイルでは、 毎回、 必ず -Wall (すべての警告を表示)オプションを付けましょう。 このオプションは、 未使用の変数、 暗黙の型変換、 条件分岐の矛盾など、 潜在的な問題を警告として出力してくれます。 C 言語では、 コンパイルが通っても動作が保証されない未定義動作も多く存在します。 したがって、 -Wall オプションは正しいコードを書くための第一歩です。
gcc -Wall と毎回タイプするのは面倒ですので、 以下で紹介するようなシェルスクリプトや Makefile を書いてタイプの手間も減らしましょう。
-Wall オプションで表示される警告がすべて消えるまで修正する
GCC の -Wall オプションで表示された警告は無視せず、 すべて修正してから次に進むことが重要です。 警告が出るということは、 コンパイラが潜在的なバグの可能性を検知しているということです。 未初期化変数や型の不一致、 条件式の誤りなど、 一見問題なく動くように見えても後に不具合を引き起こす原因になります。
警告が一つも出ない状態を維持することは、 コード品質を保証し、 保守や拡張が容易なプログラムを生み出すための基本的な習慣です。
プログラム中に printf を埋め込んで変数の値を出力させる
デバッグの基本は、 プログラム中のあちこちに printf 関数を埋め込んで、 変数の値や実行の流れを出力して確認することです。 プログラムが意図通りに動作しているかを確認するには、 特定の箇所で変数の値を表示するのが最も手軽で効果的です。
例えば、
fprintf(stderr, "x=%d, y=%d\n", x, y);
のようにして処理の途中経過を確認します。 この方法により、 論理の誤りや想定外の値が簡単に発見できます。
開発が進んだら、 これらの出力文をコメントアウトするか条件付きコンパイルで除外することで、 デバッグと本番コードを使い分けることができます。
簡潔で高速なプログラムではなく、後から保守しやすい (読みやすい) プログラムを書く
プログラムは短く書くことよりも、 誰が読んでも理解できるように書くことの方が重要です。 複雑な一行表現やマクロで高速化を狙うよりも、 意味の明確な変数名を使い、 処理を適切に関数へ分割したほうが結果的に効率的です。 読みやすいコードは、 バグの発見や修正、 将来的な拡張を容易にします。
基本的な考え方は「自分が書いたコードを、 一年後に読んでも理解できるようなコードを書く」です。 性能の最適化は本当に必要な箇所だけに絞り、 まずは正確で分かりやすい実装を優先する姿勢を持ちましょう。
「コンパイル → 実行 → テスト」の処理をシェルスクリプトで自動化する
手作業でコンパイルや実行を繰り返すのは非効率です。 これらをシェルスクリプトにまとめて自動化すると、 作業時間を大幅に短縮できます。
例えば、r というファイルに
#!/bin/sh
gcc -Wall -o ex1 array.c array_main.c
./ex1 <in1 >out1
diff -uwB exp1 out1
等と書いておきます。その後、
chmod +x r
で実行ビットを立てておけば、
./r
とタイプするだけで、 コンパイル、実行、テストを一気に行えます。
手作業でコンパイル、実行、テストを繰り返すのは面倒ですので、 こういった自動化の手法を用いないと、 自然とコンパイルやテストの頻度が減ってしまいます。
以下に説明するように、 頻繁にコンパイル、実行、テストを繰り返すのが、 簡単に、 かつ確実にプログラムを作成する近道です。
少しずつ機能を追加し、その都度テストを行い、段階的に作成する
一度に大きな機能を実装しようとすると、 どこでエラーが発生したのか特定しにくくなります。
最初は小さな単位で動作する部分を作り、 そのたびにテストをして正しく動くことを確認してから次に進むことが大切です。 段階的に開発することで、 エラーの原因を限定でき、 デバッグが容易になります。 また、 プログラム全体の理解が深まり、 修正や追加の影響範囲を把握しやすくなります。
この「小さく作って試す」姿勢は、 最終的な完成度と信頼性を大きく高めます。
途中のバージョンを残しておき、いつでも戻れるようにしておく
プログラムを修正する際には、 以前のバージョンを残しておくことが重要です。 途中まで動作していたプログラムを、 変更した結果、 「なぜか動作しなくなった」というのは本当によくあることです。 新しい変更によって動作しなくなった場合、 すぐに安定していた状態へ戻せるようにしておくと安心です。
単純にはファイルをコピーして日付付きで保存する方法もありますが、 RCS などのバージョン管理システムを利用すれば、 差分の確認や変更履歴の管理が容易です。
i, j 等ではなく、意味のある変数名をつける
変数名はプログラムの可読性に直結します。 i や j などの短い名前はループの一時変数に限定し、 その他の変数には意味を反映した名前を付けるようにしましょう。
例えば、 count、 index、 temperature のように、 何を表すのか一目で分かる名前を選ぶことが望ましいです。 これにより、 コードを作成している人 (あなたです) がコードの意図を理解しやすくなり、 バグ混入の防止にもつながります。 また、 関数名や構造体メンバも同様に、 用途や役割を自然言語的に表現することで、 全体の理解が格段にしやすくなります。
自動変数ではなく、静的変数を宣言する
C 言語における自動変数は初期化されませんが、 静的変数は初期化されます。
C 言語の初心者の頃に、 自動変数が初期化されていないことに気付かず、 間違ったプログラムを書くということが頻発します。 例えば、 関数の呼び出し回数を数えるカウンタなどが典型例です。
一般的な C 言語プログラミングでは、 コードの保守性・効率性の観点から、 静的変数よりも自動変数のほうが望まれます。 しかし、 特に C 言語の初心者の頃は、 自動変数のデメリットはメリットを大きく上まわっています。 最初の頃は、 なんでもかんでも静的変数で宣言するとよいでしょう。
メモリは動的ではなく静的に割り当てる
C 言語では malloc や free を使った動的メモリ管理が可能ですが、 初心者のうちは静的メモリ割り当てを優先する方が安全です。 配列や構造体のサイズがあらかじめ分かっている場合は、 スタック領域に確保したほうがメモリリークやダングリングポインタの心配がありません。 動的割り当ては柔軟ですが、 解放忘れや多重解放などの危険を伴います。
静的割り当てを基本とし、 必要に応じてのみ動的メモリを使うという設計方針を持つことが、 堅牢なプログラム作成につながります。
エディタのタグジャンプ機能を活用する
関数定義や変数宣言の場所をすぐに参照できる「タグジャンプ」機能は、 コードの理解と保守を大幅に効率化します。 ctags や etags を使ってタグファイルを生成すると、 エディタ内で関数名にカーソルを合わせてすぐに定義へ移動できます。
特に大規模なプログラムでは、 関数間の依存関係を追う作業が頻繁に発生するため、 この機能を使うことで検索の手間を大きく減らせます。 読みやすさだけでなく、 理解の速さも「効率的なプログラミング」の要素です。
エディタのエラー箇所へのジャンプ機能を活用する
コンパイル時に出力されるエラーメッセージから、 該当箇所へ一瞬で移動できる機能を活用すると、 デバッグ作業が大幅に効率化します。 多くのエディタでは、 GCC の出力フォーマットを自動認識し、 エラーの行番号にジャンプするショートカットを備えています。
エラー文を読みながら手でファイルを開き、 行番号を探すといった無駄な作業を減らせるため、 修正と再コンパイルのサイクルを高速に回せます。 短時間での反復が可能になることで、 思考を途切れさせずに問題を解決できます。
コメントには HOW (何をしているか) ではなく WHY (何のためにしているか) を書く
コメントには「何をしているか」ではなく「なぜそれをしているか」を書くことが大切です。 コード自体を読めば処理内容 (HOW) は理解できますが、 設計上の意図や選択理由 (WHY) はコードだけでは伝わりません。 たとえば「この関数は性能を優先して再帰を使わない」など、 判断の背景を書くことで、 将来の変更時に不要な誤解を防げます。 コメントは説明書ではなく、 思考の痕跡として残すものです。 後から自分や他人が読むことを意識して、 意図を明確に記録しましょう。
GCC のエラーメッセージを日本語ではなく英語にする
GCC のエラーメッセージは英語で表示させましょう。
理由は、 エラー内容を正確に理解し、 他の開発者や検索エンジンを通じて情報を得やすくするためです。 日本語化されたメッセージは一見親切に見えますが、 翻訳のゆらぎや専門用語の誤訳により、 本来の意味を誤解することがあります。 英語のままであれば、 エラー文をそのままコピーして検索でき、 Stack Overflow や GCC の公式ドキュメントなどで直接対応策を探すことができます。
もし環境で日本語が表示されている場合は、 端末で export LANG=C または export LC_ALL=C を設定することで英語表示に戻せます。 特に教育や研究の現場では、 英語のメッセージに慣れておくことが、 将来の開発力向上につながります。
make を使ってコンパイルを自動化する
複数のソースファイルを手作業でコンパイルするのは非効率です。 make を使えば、 依存関係を自動的に解決し、 必要な部分だけを再コンパイルできます。 Makefile にルールを定義しておくことで、 make コマンド一つでビルドが完了します。 特に -Wall オプションの付与やライブラリのリンク設定を自動化することで、 コンパイルミスの防止と作業時間の短縮を同時に実現できます。
例えば、 作業ディレクトリに以下のような内容の Makefile という名前のファイルを作成しておきます。
CFLAGS = -Wall
all: test1
list1test: list1test.o list.o
test1: list1test
./list1test <in1 >out1
diff -uwB exp1 out1
clean:
rm -f list1test list1test.o list.o
TAGS:
etags *.c *.h
こうしておけば、
make
と実行するだけで、 必要に応じて list1test をビルドし、 list1test プログラムの実行と、 diff によるテストを自動的に行ってくれます。
上記の Makefile では、
list1test は、list1test.o と list.o というファイルに依存している。
test1 というターゲットは、list1test にファイルに依存している。
ということを記述しています。make は暗黙のルールを持っていますので、list1test.o や list.o を作成するために勝手に C コンパイラを呼び出してくれます。
デバッガ (GDB) を活用する
GDB (GNU Debugger) は、 機械語プログラムの動作を逐次追跡できる強力なデバッグツールです。 C 言語で書かれたプログラムをデバッグする場合には、 C のソースコード中にブレークポイントを設定して変数の値を確認したり、 行単位のステップ実行で処理の流れを分析したりできます。 printf デバッグよりも効率的にバグの原因を突き止められるため、 日常的に使えるようになると大きな武器になります。 特に、 セグメンテーションフォールトなどの例外が発生した原因を調べる際には欠かせません。
C 言語が絶対に必要でなければ Python で書く
プログラミングの目的が「動くものを作る」ことであるならば、 言語にこだわりすぎる必要はありません。 C 言語は高速で制御性が高い反面、 メモリ管理や型指定など低レベルの作業が多く、 開発効率は低下しがちです。 OS やデバイスドライバなど、 「C 言語でなければ書けないプログラム」を開発するのでなければ、 ほとんどの場合、 Python のような高水準言語を用いた方が短時間で安全に開発できます。 C 言語を選ぶのは、 性能や組込み制約など明確な理由がある場合に限るべきです。 目的に応じて適切な道具を選ぶことこそが、 真に効率的なプログラミングです。