処理でブロックに分けるか、変数でブロックに分けるか〜コードは「書く人」ではなく「読む人」に最適化する

実装中は、例1(同じ処理ごとにブロックに分ける)で進めて、最後に 例2(変数ごとにブロックに分ける) に整理することが多い。この2つの書き方について、考察しました。

例1:同じ処理ごとにブロックに分ける

# + 9 * 60
real_start_time = talk_dict["start_time"] + 9 * 60
real_end_time = talk_dict["end_time"] + 9 * 60

# divmod
start_hour, start_minute = divmod(real_start_time, 60)
end_hour, end_minute = divmod(real_end_time, 60)Code language: Python (python)

例2:変数ごとにブロックに分ける(推奨)

# talk_dict["start_time"] → real_start_time → start_hour, start_minute
real_start_time = talk_dict["start_time"] + 9 * 60
start_hour, start_minute = divmod(real_start_time, 60)

# talk_dict["end_time"] → real_end_time → end_hour, end_minute
real_end_time = talk_dict["end_time"] + 9 * 60
end_hour, end_minute = divmod(real_end_time, 60)Code language: Python (python)

書く人、読む人の思考モード

例1:書く人の思考モードに合っている

人間がコードを書くときは「同じ種類の作業をまとめてやりたい」 という脳の欲求があります。

1. まず「開始と終了に、ベースの時間を足すロジック(同じ概念)」を2行並べて書きたい
2. 次に「それを時間と分に分解するロジック(同じ概念)」を2行並べて書きたい

これはコードを書いている最中としてはごく自然で、正しいステップです。

例2:縦のスコープ(生存期間)が短い - 読む人の思考モードに合っている

例2のメリット:

real_start_time という変数は、作られた次の行の divmod で使われ、それ以降はもう登場しません。脳のメモリ(認知負荷)からすぐに消去して、次の end_time の処理に集中できます。

例1のデメリット:

real_start_time を作ったあと、関係のない real_end_time の計算が1行挟まります。コードが長くなった場合、「あれ、この変数はどこで使われるんだっけ?」と視線を上下に往復させる必要が出てきます。

リファクタへの影響

例1の構造からリファクタしようとする場合(横切りのメソッド)

例1の構造のまま、見た目の行数を減らそうとして「横のレイヤー(足し算の層、割り算の層)」でナイフを入れてしまうと、「使い回せない、引数だらけの密結合な関数」が誕生してしまいます。

real_start_time, real_end_time = add_offset(talk_dict["start_time"], talk_dict["end_time"], 9 * 60)

start_hour, start_minute, end_hour, end_minute = split_to_hour_and_minute(real_start_time, real_end_time)Code language: Python (python)

このパターンを 「Temporal Cohesion(時間的凝集)」 と呼びます。

「同じタイミングで実行するから」という理由だけでまとめられた関数(例:add_offset や split_to_hour_and_minute は、中のロジックではなく、呼び出し側の都合に100%依存してしまうため、引数が雪だるま式に増え、少しの仕様変更で簡単に壊れてしまいます。

また、この方法で分けたメソッドのテストを書こうとすると、引数の準備が大変なのに、テストの説明文がうまくまとめられないことが多くなります。

テスト名: startとendに540を加えたら期待通りになること
開発者の本音: 「そりゃコードに `+ 540` って書いたんだから、そうなるに決まってるじゃん(笑)」

原因は、テスト対象の関数が「ビジネスロジック(仕様)」ではなく「実装の手続き(プログラムの都合)」で切られているからです。

横切りされた add_offset(start, end, offset) のような関数は、「足し算という計算手順」そのものでしかないので、テスト名を書こうとすると「AとBを足したらCになること」という、コードを日本語に翻訳しただけの意味のない説明になってしまいます。

例2の構造からリファクタしようとする場合(縦切りのメソッド)

例2のまとまり(関心の局所化)ができていれば、「開始時間」または「終了時間」という1つのタイムスタンプ(整数)だけに関心を持つ、シンプルで独立した関数を作ることができます。

# 呼び出し側:引数が少なくて超スッキリ
start_hour, start_minute = convert_to_hhmm(talk_dict["start_time"])
end_hour, end_minute = convert_to_hhmm(talk_dict["end_time"])Code language: Python (python)

これなら、convert_to_hhmm はこのプログラムのどこからでも、あるいは他のプログラムにコピペしても動く「疎結合(独立した)」なパーツになります。

まとめ:コードは「書く人」ではなく「読む人」に最適化する

「書くときの脳の動き」と「読むときの脳の動き」は、まったく別のモードです。

例1・書くとき(処理で分ける):

「同じパターンの処理」をコピペや連続入力で一気に片付けたい、「書く人(作者)」の思考順序に最適化されたコード。

例2・読むとき(変数で分ける):

「1つのストーリー(塊)」ごとに上から下へ一気に理解したい、「読む人(レビュワーや未来の自分)」の理解速度に最適化されたコード。

自分でコードを書いているときは例1の「処理で分ける」の方が圧倒的にスピードが出るので、そこで無理に例2の「変数で分ける」にしようとすると、かえって思考のブレーキになってしまいます。

そのため、「最初は脳の赴くままに例1で書き、コミットやPR(プルリクエスト)を作る前に『読む人の認知負荷』を優先して、例2に整理して仕上げる」のが、開発効率とコード品質を両立させるベターなアプローチです。

タイトルとURLをコピーしました