「説明変数」と分かりやすいコードを書くことの価値

過去に「読み手に詳細が伝わらない変数名を書くな」っていう記事を書いたことがあるけど、今回はその発展系というか、焼き直しというか。

名著「リーダブルコード」に「説明変数」という言葉が出てくる。統計学の文脈で「説明変数と目的変数」といった用いられ方がするモノとは全く別の言葉。

今回はこの「説明変数」という言葉を用いて、読み手にとって分かりやすいコードというモノがどうしてそれほど重要視されるのか、というところを整理してみる。

目次

筆者の立ち位置と前提整理

この記事を書いている僕の立ち位置と、そんな僕が何を重視している人間なのか、という前提を整理しておく。

こういう出自の自分は、プログラミングにおいて次のようなことを重視している。

こういうことを重視しているので、僕は「読み手に詳細が伝わらない変数名を書くな」といったことをクドクド書いていたし、今回の記事でも書く。

だもんで、趣味でプログラミングしている人とか一人で開発している人とか、「書いたコードを他の人が保守するなんて場面は絶対に発生しません」といった人は、僕がこれから話すことはもしかしたら重視されないことなのかもしれない。でも、そういう人達にとっても決して無意味なことでもないと思っている。

それでは、職業エンジニアとして、こういうことを重視するのはなぜなのか、順に説明していく。

プログラムは「書いている時間」より「実行される時間」「読まれる時間」の方が長い

まずは当たり前のところから。プログラム、コードというモノは、「プログラマがコードを書いている時間」よりも、「そのプログラムが実行される時間」や「そのコードを自分や他の人が読んでいる時間」の方が長いモノなのだ。

このように、作るのにかかる時間よりも、利用する時間の方が長いからこそ、わざわざコードを書いてシステム化・自動化することに意味・効果が出てくる。

しかし、一度書いたコードを一切変更せずに何年・何十年と運用し続けることは不可能に近い。対象システムの機能追加・機能改修の依頼が舞い込んでくることもあれば、そうでなくとも経年により周りの状況の方が変わるので、それに合わせる必要が出てくる。

そういった理由で、コードを書いていた当初は「完璧なモノができた!もう二度とこのコードに触れることはないぞ!」と思っていても、リリースして1・2年、いや数ヶ月くらいで、もう一度そのコードを開いて編集する必要が出てくるモノなのだ。

既存のコードを読み、どこに何を書けば要望を満たせるのか調査を始めるワケだが、すると今度は、コードを書いていた本人でさえも当時どういうつもりでそんなコードを書いたのか覚えておらず、改修方法がすんなり出てこない、といった事態が容易に発生する。人間は忘れる生き物だ。自分の渾身のコードでも、書き終えたら細かいことは忘れてしまうし、当時は想定していなかったような内容を付け加えることになるワケで、仮に細かいことを全部覚えていたとしても、改修は難しいことなのだ。

最初のコードを書いた本人がこんな調子だ。では、「機能改修案件」が始まったことで新規参画させられた開発メンバはどうだろう。メチャクチャ当たり前のことを言うが、新規参画したメンバは「当初のコード」を書いていないので、全体を読み解く必要が出てくるのだが、その人の労力を想像できるだろうか。つまり「当時の自分はこう考えて書いていたと思う」という予備知識がない状態の人達がコードを読解するワケで、そのコードの書き方が悪かったら、どれだけ苦労するだろうか。

さて。

「当時急いで書いていたんで、変数名とかテキトーです、インデントも間違いまくってます、どの関数がどこに影響しているか分かりません」

こんなコードを渡されて、「なんとか読解して必要な修正を入れてください。もしも修正内容が間違っていてバグらせたら損害賠償です」と言われる怖さが、少し想像できただろうか。「当時の自分は何を考えてこんなコード書いてたんだよぉ!!」という過去の自分への怒りも湧くかもしれない。自分がその現場を離れていて、他の人が改修案件を担当していたら、「あの野郎、クソコード残して逃げやがって」と嫌われるかもしれない。「成果物の品質が著しく悪い」となったら、人事評価に響く恐れもある。

こうならないためには、プログラムを書いている段階から、実行される時のこと、未来の自分や他の人がこのコードを読む時のことを考えていかに読み手に配慮したコードを最初から書けるかが重要なのである。

読み手に伝わりにくいコードが生まれる根本原因

世のウンコードのどこが悪いのか。「ガード節がなくインデントが多い」「グローバル変数を多用している」などなど、プログラミング的なテクニックからの指摘もできるが、それらの根本原因は一つである。

そのコードを書いている瞬間の、書き手の思考しかコードに残されていないのが根本原因である。

どういうことか。


そのプログラマは、今まで Java の経験しかなく、Python を使った案件は初めてで文法を調べながらコーディングしていたかもしれない。突然金融系の会社の案件に参画させられて、金融業の専門知識がないまま、それらを処理するシステムの開発を任されたかもしれない。上長のサポートが不十分で、分からないことを質問できる場がなく、独力でこなすしかなかったのかもしれない。

時間がない、知識がない、余裕がない。原因はともあれ、そうした目の前のことで精一杯な人は視野が狭い状態である。そんな状態では、独りよがりで自分本位なコードしか書けないだろう。

しかし、そもそも、コードを書いたり、文章を書いたりする時に、「他人目線」で物事を考えながら書いたことがない、という人も、想像以上に存在するっぽい。「視野が狭い」状態がデフォルトの人生だった人達だ。かなり恐ろしいけど、「自分がコードを書く」ことと「読み手のことを考えること」がどうして関係するのか、全く想像もつかないという人種も、かなり見てきたので、その辺ももう少し深堀りしてみる。

「読み手にどう思わせたいか」が表現できていれば良いコード

さて、職業エンジニアがプログラムを書くにあたって、

という話は理解していただけたと思う。そして、

という話もした。

それではどういうコードが良いコードなのか、一言でいえば、読んだ人に「このように思わせたい」という意図が表現できていて、その意図したとおりに読み手に理解してもらえるコード、といえるだろう。


例えば、懇親会などの招待状を作るときのことを考えてみる。

招待状ひとつ取っても、「相手に参加可否を答えてほしい」という「期待するアクション」があるワケで、それが相手に伝わるように招待状を書かなければ、相手は参加可否を答えないかもしれないし、期日を知らないがために回答が遅れるかもしれない。

会話、電話、メール、文書のやり取り、コーディング、そうしたあらゆる情報伝達は、送信側が、相手に何らかのアクションを期待しているから行われる、という根本をしっかり押さえ直そう

コーディングも、それと同じだ。いつかこのコードを読んだ人が、この関数やクラスをどういうモノだと思ってくれるだろうか? それをよくよく想像し考えながら、読み手に「コレはこういうモノなんですよー」「この関数で事前処理をして、こっちの関数で後処理をしているんですよー」などと解説するかのようにコードを書いていくのだ。

悪いコード例から「説明変数」を説明する

読み手に「こう捉えてください」「こう思ってください」と、解説・説得するように、コードを書くことが、読みやすい・分かりやすいコードに繋がる。

書き手の意図を読み手に伝えるための、一番基礎的なテクニックの一つが「説明変数」である。やっと出てきた。w

例えば、とある帳票を出力するプログラムを考える。書き手としては、

なんて考えてコーディングをしていたが、思考が整理しきれておらず、またプログラミングの基礎スキルも不足していたとする。書き手の思いとは裏腹に、実際のコードが以下のようなコードだったらどうだろうか。

function print(t) {
  let w = new Sheet();
 let g = w.addRow()
   g.addColumn();g.addColumn();g.addColumn();
  a = fetch();
  g = w.addRow();
  if ( t == '1') {
    c=g.addColumn(); c=g.addColumn(); c2=g.addColumn();
    c.mergeColumns(c2);
    } else {
    g.addColumn();
    g.addColumn();
    g.addColumn();
  }
  dl(w);
}

function dl(s) {
  s.getRow(0).getColumn(0).setValue(a.namae);
  s.getRow(0).getColumn(1).setValue(a.age);
  s.getRow(0).getColumn(2).setValue(a.brtd);
  s.getRow(1).getColumn(0).setValue(a.no);
  if(s.getRow(1).getColumn(2)==null) {
    s.getRow(1).getColumn(1).setValue(a.address);
  } else{
    s.getRow(1).getColumn(1).setValue(a.country);
    s.getRow(1).getColumn(2).setValue(a.address);
  }
  Printer.print(s);
}

適当にこしらえたウンコードではあるが、コレでもパッと見での文句は色々あると思う。

print() 関数がレイアウトを作っていて、dl() 関数が値を埋め込んで実際に帳票を出力する関数…のつもりで作っている。帳票に書き込むテキストデータは変数 d に格納されているのだが、分かっただろうか。

コレはかなり下手に書いた例だが、プログラミングのテクニック的なこと以前に、コード全体から、読み手に伝わりやすくしようとする工夫が見られないのが大きな問題である。こういう状態では、そもそもコードを書く際の心持ちが足りていないので、個々のプログラミングテクニックを聞きかじったところで正しく適用できず、誤った「改善」をしてしまい余計に分かりにくくなることだろう。

設計書が別にあるとしても、コードを読む人間にとって知りたいのは、こういう内容ではないだろうか。

「説明変数」を使って良いコードに書き直してみる

さて、それでは「説明変数」という考え方で、上述のコードを書き直してみよう。

「説明変数」というのは簡単なことで、変数名によって対象や意図を説明してあげるということだ。

前述の悪いコードの「名前」に関する問題点は、ざっとこんなところがあるだろう。

これらを踏まえて、変数名・関数名とインデントを中心に修正してみよう。なお、今回はもっと根本的に設計からやり直した方が良いと思われる題材だが、やんごとなき事情によって大幅なリファクタリングが禁じられている案件を想定して、比較的小規模な修正でできるだけ読みやすくなるように工夫した。

/**
 * 帳票の種別コードマスタ
 * 
 * HACK : 画面上で利用しているコード値が帳票にも紐付いてしまっているため、現状は仕方なく数値の文字列を利用している。
 *        それだと分かりにくいためこの定数で名前を付けてあるが、画面機能と切り離して分かりやすい種別体系にしたい。
 */
const workSheetTypes = {
  japanese  : '1',
  fromAbroad: '2'
};

/**
 * 帳票のレイアウトを作成するプライベート関数
 * 
 * @param {number} workSheetType 帳票の種別
 * @return {Sheet} レイアウトを組み立てた帳票オブジェクト
 */
function _createWorkSheetLayouts(workSheetType) {
  const workSheet = new Sheet();
  
  const firstRow = workSheet.addRow();
  const numberOfColumnsInTheFirstRow = 3;
  for(let num = 0; num < numberOfColumnsInTheFirstRow; num++) {
    firstRow.addColumn();
  }
  
  const secondRow = workSheet.addRow();
  const secondRowFirstColumn  = secondRow.addColumn();
  const secondRowSecondColumn = secondRow.addColumn();
  const secondRowThirdColumn  = secondRow.addColumn();
  const isJapaneseWorkSheetType = workSheetType === workSheetTypes.japanese;
  if(isJapaneseWorkSheetType) {
    secondRowSecondColumn.mergeColumns(secondRowThirdColumn);
  }
  
  return workSheet;
}

/** 帳票の入力データとなる人物情報を表現する Value Object */
class Person {
  constructor(name, age, birthdate, applicationNumber, isJapanese, country, address) {
    this.name              = name;
    this.age               = age;
    this.birthdate         = birthdate;
    this.applicationNumber = applicationNumber;
    this.isJapanese        = isJapanese;
    this.country           = country;
    this.address           = address;
  }
}

/**
 * 帳票に値を設定するプライベート関数
 * 
 * @param {Sheet} workSheet レイアウト組み立て済みの帳票オブジェクト
 * @param {number} workSheetType 帳票の種別
 * @param {Person} person 設定する値を持った人物情報
 * @return {Sheet} 値を設定した帳票オブジェクト
 */
function _setValuesInTheWorkSheet(workSheet, workSheetType, person) {
  const firstRow  = workSheet.getRow(0);
  
  const nameColumn      = firstRow.getColumn(0);
  const ageColumn       = firstRow.getColumn(1);
  const birthdateColumn = firstRow.getColumn(2);
  nameColumn     .setValue(person.name     );
  ageColumn      .setValue(person.age      );
  birthdateColumn.setValue(person.birthdate);
  
  const secondRow = workSheet.getRow(1);
  
  const applicationNumberColumn = secondRow.getColumn(0);
  applicationNumberColumn.setValue(person.applicationNumber);
  
  const isJapaneseWorkSheetType = workSheetType === workSheetTypes.japanese;
  if(isJapaneseWorkSheetType) {
    const addressColumn = secondRow.getColumn(1);
    addressColumn.setValue(person.address);
  } else {
    const countryColumn = secondRow.getColumn(1);
    const addressColumn = secondRow.getColumn(2);
    countryColumn.setValue(person.country);
    addressColumn.setValue(person.address);
  }
  
  return workSheet;
}

/**
 * 「申請書」帳票を出力する
 * 
 * グローバルの `fetch()` 関数により、現在のセッションから人物情報を取得し
 * 「申請書」帳票を組み立てダウンロード可能にする
 */
function printApplicationForm() {
  const rawResponse = fetch();  // 人物情報を外部から取得する関数のテイ
  const person = new Person(rawResponse);
  
  // 帳票の種別を判定する
  // HACK : 帳票の種別を「国内向け」「国外向け」で分類しているが、画面側の処理と切り離せたら
  //        素直に2種類の帳票出力機能に分けた方が拡張性が上がると思われる
  const isJapanese = person.isJapanese;
  const workSheetType = isJapanese ? workSheetTypes.japanese : workSheetTypes.fromAbroad;
  
  const workSheetWithLayouts = _createWorkSheetLayouts(workSheetType);
  const workSheetWithValues  = _setValuesInTheWorkSheet(workSheetWithLayouts, workSheetType, person);
  
  Printer.print(workSheetWithValues);
}

コレでもまだ全然ツッコミどころはあると思うのだが、一番感じ取ってほしいのは、関数名や変数名によって、どんな処理をしているのか読み取りやすくなったというところ。

改善後のコードを細かく見ていく

「説明変数」を用意したことで分かりやすくなったと思われる点をいくつか抜粋してみる。

// 関数名により、帳票の「レイアウト」を作る関数であることが分かりやすくなった (元は `print()` 関数だった)
// 引数も `workSheetType` という「種別」を示す変数であることが分かる
function _createWorkSheetLayouts(workSheetType) {
  // 1文字変数で横着せず、「ワークシート」と書いた
  const workSheet = new Sheet();
  
  // ワークシートの1行目を示すオブジェクトとして First Row と書いた
  const firstRow = workSheet.addRow();
  
  // 「1行目には3列のセルを配置したい」ということで、「3」というマジックナンバーに説明変数を用意した
  // 元は `addColumn()` を3回実行していたが、コレで「3列用意する」ことが重要であることが伝わるようになった
  // さらに、1行目の列数を増やしたい場合はこの定数の値を 3 から増やすだけで対応できることも分かる
  const numberOfColumnsInTheFirstRow = 3;
  for(let num = 0; num < numberOfColumnsInTheFirstRow; num++) {
    firstRow.addColumn();
  }

一番「説明変数」として分かりやすくなったのは、numberOfColumnsInTheFirstRow だろう。変数名としてはいささか長過ぎるかもしれないが、3 が1行目の列数を意味することは間違いなく伝わるようになった。

function _createWorkSheetLayouts(workSheetType) {
  // 引き続き…
  
  // 同じ `addRow()` でも、2行目を指していることが分かるように Second Row と書いた
  const secondRow = workSheet.addRow();
  // 元は `if` 文の中に一部同じ処理が混ざっていたが、
  // `workSheetType` の種別に関わらず同じ動きをするコードは `if` 文の外に出した
  const secondRowFirstColumn  = secondRow.addColumn();
  const secondRowSecondColumn = secondRow.addColumn();
  const secondRowThirdColumn  = secondRow.addColumn();
  // 引数 `workSheetType` の値は `workSheetTypes` というコードマスタと対応することが読み取れる
  const isJapaneseWorkSheetType = workSheetType === workSheetTypes.japanese;
  if(isJapaneseWorkSheetType) {
    // 「2行目の2列目」と「2行目の3列目」を結合していることが変数名からも分かる
    secondRowSecondColumn.mergeColumns(secondRowThirdColumn);
  }

if 文の条件に名前 (isJapaneseWorkSheetType) を付ける、というのが分かりやすくなるところかも。今回の例だとちょっと分かりにくいかもしれないが、他の例でいうと、

if(person.age >= 20 && person.gender === '1') {
  // ……
}

このような条件文だと、どうしてこういう条件になっているのか、'1' という値をコーディングしていて本当に正しいのかどうか、分かりにくいところがあると思う。

コレを、次のように直したらどうだろう。

// 予め、「男性を示すコード値は 1」「女性を示すコード値は 2」と明確に定義しておく
// ちなみに性別の表記に関しては ISO 5218 という国際規格があるので、コレに合わせた値を使っておくと間違いが減らせるだろう
const genderCodes = {
  male  : '1',
  female: '2'
};
// 予め、「成人とみなす開始年齢」を明確に定義しておく
const adultAge = 20;

// 成人しているか否か
const isAdult = person.age >= adultAge;
// 男性であるか否か
const isMale = person.gender === genderCodes.male;

if(isAdult && isMale) {
  // 成人男性のみの処理……
}

if 文の部分を見ると、「もし、成人していて、かつ、男性なら…」と自然に読める文章になっていることが分かるだろう。コレが説明変数の効果である。ついでに、法改正で「成人とみなす年齢」が変更になった場合は、定数 adultAge のマジックナンバーを修正するだけでよく、コードの見通しがよくなっていることが分かるだろう。

単にコードの行数を短くすることしか考えていないと、前者のようなコードを書きがちである。しかし、行数がかさんでいる後者のコードの方が、意味が伝わりやすく、読みやすくはないだろうか? コードの読みやすさは「行数が少なければいい」というモノではなく、意味がすんなり理解できれば行数が長くなったって問題がないのである。

class Person {
  constructor(name, age, birthdate, applicationNumber, isJapanese, country, address) {

元のコードは namae (「名前」…!)、brtd (何の略?)、no (何のナンバー?それとも「ノー!」の意味?) という風にクソコード感満載だったが、ちゃんと名前を付けてあげた。birthday (誕生日) と birthdate (生年月日) は意味が違うので、一般的な英単語の意味は辞書を引いて確認すること。

名前付けによってコードの補足解説をしていくのが「説明変数」なので、今回のような「申請書に書く生年月日」のつもりで birthday と書いてしまうとおかしなことになる。「誕生日」というと、楽天市場のような EC サイトにある「誕生日割り引きクーポンの発行」とか、そういう違う機能を想像させてしまいかねない。

情報が相手に間違いなく伝わるのは、相手と同じ知識・共通認識を利用して話している時だけである。相手が持っているであろう常識や基礎知識に沿わない、自分だけの思い込みだったり、自分が勘違いしているモノを確認せず使ってしまったりすると、相手に誤解を与える。「ココは生年月日欄だから、変数名は『バースデー』だ!」なんて間違えていると、「説明変数」は意味を成さない。

function _setValuesInTheWorkSheet(workSheet, workSheetType, person) {
  // 1行目を取り出す
  const firstRow  = workSheet.getRow(0);
  
  // 1行目の各カラムは何の欄なのか、変数名で説明している
  const nameColumn      = firstRow.getColumn(0);  // 名前欄
  const ageColumn       = firstRow.getColumn(1);  // 年齢欄
  const birthdateColumn = firstRow.getColumn(2);  // 生年月日欄
  // それぞれのカラムに、引数 `person` の情報を設定している
  nameColumn     .setValue(person.name     );
  ageColumn      .setValue(person.age      );
  birthdateColumn.setValue(person.birthdate);

垂直アラインメントは好みが別れるというか、自動フォーマッタなんかはまず対応していない整形方法なので、真似するかどうかはお任せする。それよりも重要なのは、

ということが、説明変数によって分かるようになったことだ。コレがもし

ageColumn.setValue(person.birthdate);

なんて書いてあったら、「値をセットするカラムが間違ってるんじゃない?」と気付きやすいコードになるだろう。

function _setValuesInTheWorkSheet(workSheet, workSheetType, person) {
  // 引き続き…
  
  if(isJapaneseWorkSheetType) {
    const addressColumn = secondRow.getColumn(1);
    addressColumn.setValue(person.address);
  } else {
    const countryColumn = secondRow.getColumn(1);
    const addressColumn = secondRow.getColumn(2);
    countryColumn.setValue(person.country);
    addressColumn.setValue(person.address);
  }

workSheetType という気持ち悪い分岐を少しでも和らげたい思いはつたわるだろうか。w

ココでは、if ブロック内と else ブロック内とで addressColumn が示すカラム位置が異なる、という点が読み取れる。セル結合してあるタイプの帳票は address だけ設定して、セル結合されていないタイプの帳票は国名と住所を設定する、というワケだ。この辺も、説明変数による名前付けが、この手の帳票ライブラリでありがちな Index (添字) の操作を多少分かりやすくできているところだと思われる。

function printApplicationForm() {
  // 中略
  
  const workSheetWithLayouts = _createWorkSheetLayouts(workSheetType);
  const workSheetWithValues  = _setValuesInTheWorkSheet(workSheetWithLayouts, workSheetType, person);
  
  Printer.print(workSheetWithValues);
}

最後に、新しい関数ではあるが、「Application Form」を「Print」する関数であることがひと目で分かる、printApplicationForm() 関数を用意した。コレがパブリックな関数である。

元のコードは workSheetTypeperson の情報が微妙に混ざり込んでいて、print()dl() 関数の呼び出し順やスコープが分かりにくかったが、改善後のコードは、外部から呼び出す際は printApplicationForm() 関数を叩くだけで良いことが分かりやすくなったと思う。

2つのプライベート関数についても、コードの行数を節約しようと思えば

Printer.print(_setValuesInTheWorkSheet(_createWorkSheetLayouts(workSheetType), workSheetType, person));

こんな風に、変数を使わずに1行にまとめて書いてしまうこともできる。しかしコレでは、処理順が一見して分かりにくく、デバッグもしづらい。

原始的な「Print デバッグ」を書き足して表現すると、改善後のコードはこういう形で、一つひとつの中間処理の妥当性をチェックしやすい。

function printApplicationForm() {
  // 中略
  
  console.log('帳票の種別 : ', workSheetType);
  
  const workSheetWithLayouts = _createWorkSheetLayouts(workSheetType);
  console.log('帳票のレイアウトを組み立てた', workSheetWithLayouts);
  
  const workSheetWithValues  = _setValuesInTheWorkSheet(workSheetWithLayouts, workSheetType, person);
  console.log('組み立てた帳票に値を設定した', workSheetWithValues);
  
  Printer.print(workSheetWithValues);
}

サンプルコードでいうと、以上のような感じで、「説明変数」、名前付けによってだいぶマシなコードにできたと思う。

他の説明変数の例

その他に説明変数が有効な例でいうと、JavaScript の場合 map()filter() のような高階関数を使う時だろうか。

const files = fs.readdirSync('./example-directory', { withFileTypes: true }).filter(x => x.isFile()).map(x => x.name);

fs.readdirSync() というのは、Node.js の fs モジュールが持つ API で、指定のディレクトリ配下のファイル一覧を取得できるモノ。ココでは簡単にするため同期型の API で書いている。

このコードではその後に filter()map() があるのだが、引数 x が何を意味するのか分からないし、最終的に取得できた const files とはファイルオブジェクトなのか、ファイルのフルパスなのか、何なのかが分かりにくい。少なくとも、Node.js の API を理解して、コードを追って、「コレってこういう意味だよな」と整理しないといけないので、たった1行に対して、読み手が理解するための負荷が極めて高い。

同じ処理だが、こんな風に書き直してみたらどうだろう。

const dirents = fs.readdirSync('./example-directory', { withFileTypes: true });
const direntsFileOnly = dirents.filter(dirent => dirent.isFile());
const fileNames = direntsFileOnly.map(dirent => dirent.name);

filter() 内の引数名が x ではなく dirent となるだけでもだいぶ分かりやすくなるし、「ディレクトリを除外しファイルだけ抽出する」「ファイル名を取得する」といったコメントを書かずとも、コードだけで相当伝わるようになったと思う。

「変数の命名をサボれば早くコードが書ける」なんてウソ

ところで、よく「急いで書いたので、変数名とかテキトーです」みたいな戯言を言う輩がいるのだが、僕に言わせればそんなの 100% ウソである。変数の命名に時間を割かなければ早くコードが書けるだなんて、ありえない。

変数名をパッと上手く付けられず、1文字の変数名にしたり、info とか list とか書いて「何の情報?」「何のリストなの?」と思わせているような状態というのは、どんな処理を実装するのかという言語化ができていないままコードを書き始めているだけである。設計書を書いていないか、書いていても日本語の時点で適切な言語化ができていないか、誰かが書いてくれた設計書をまともに読めていないか、のどれかである。

どんなモノを作るか、どういう登場人物が出てくるか、どういう筋道を立てて処理を進めていくのか。「シナリオ」がちゃんと描けていれば、「ココで人がでてきてー、情報をリストに追加するー」なんて表現の仕方はしないはずなのである。「ログイン中のユーザ情報をセッションから取得し、ログイン履歴一覧の配列先頭に追加する」というように、何をどうする、ということが細かく言えるはずなのだ。

それを細かく書けないというのは、どう考えても「何を実装したらいいのか分かっていない」のと同じ状況だし、明らかに読み手のことを考えていない。

というか、「最初の読み手」って、書き手本人なんだけどな? 書いたコードに対するテストコードを書いて直して、コードレビュー依頼を出すまでの間に、書いた本人がコードを読むターンが必ずあるはずなのだ。そこで自分自身がちゃんと分かって書いていることを、どこで確認しているのだろうか? その後にコードレビューを依頼するレビュアーは、どこをどう見て、どうして正しいと再鑑するというのだ? 自分の正しさを他人に確認してもらおうっていうんだから、その時点で少なくとも自分本人が理解していて、レビュアーには理解してもらえるコードじゃないといけないのに。

命名をサボると早く書ける、というのは、「自分自身の理解もおざなりなまま、とりあえずレビュー依頼を出すまでの時間が短く済む」ことを指しているのか?だとしたらバカげている。それは「実装が終わった」とは言わない。汚れがキレイに落ちたかどうか確認していないけど雑巾がけが終わったと主張しているようなモノだ。

こういう輩に、「テキトーに書いたのは変数名だけですか?じゃあ変数名だけ今から時間かけてリファクタリングしてください」と依頼してみると、

こんな調子で、こういう輩がまっとうなコードを出してくることは絶対にない。時間があるからないからではなく、頭が足りていないから常にクソコードが出てくるだけなのを言い訳しているに過ぎないのだ。何を作ったらいいのかも分かっていないし、適切な命名が何たるかも分かっておらず、そんなクソコードを読むハメになる他人の気持ちも考えられないバカであることをさっさと認めて辞めてくれ。

僕は、「設計書」をまともに書かないような個人的なスクリプトでも、ドラフトの状態から変数名をちゃんと考えて書いている。list だの item だの、意味不明な変数名を付けてしまったら、5分後の自分がどう困るか目に見えているからだ。「リストってなんだろう…?」とサッパリ記憶喪失になってしまうワケではなくて、「このリストはファイル名の一覧だったと思うんだけど、拡張子は除去してあるんだっけ、ないんだっけ?」というように、細かな仕様に自信が持てなくなるから、である。

もしもその変数名が list なんかではなく baseNames と書かれてたら、「拡張子を除去したファイル名の一覧」であることが確実に分かる。後からデバッグしたりテストコードを書こうと思った際も、チェックすべき観点が確実になる。すなわち「この配列には拡張子の文字列が含まれていないこと」と。

そんなチマチマやってたらコーディングに時間がかかるのでは?と思われるかもしれないが、命名によって処理内容が明確になっていくので、手戻りが発生しにくく、後で関数切り出しなどしようと思った時も切り貼りが容易だったりして、とにかく時間がかからない。元来タイピング速度がそこそこ速いのもあるし、IDE による入力補完なども駆使して、誰よりも早く、最初からレビュー指摘ゼロのコードを書けている。入力文字数が数文字増える程度の「誤差」で、早くコードが書けるとか遅くなるとか言わないでくれ。そんなことキーボード触ったことないド素人でもない限りありえないから。

自分の経験上、コードレビュー指摘が多かったのは、自分の知らない業界のシステム開発に関わったりしていて、何を実装すべきか理解がしきれていない時だった。月末に起動する基幹バッチの存在を知らずして実装できないような関連システムだとか、顧客データの扱いに法的な決まりがあって、そのとおりにデータを加工しないといけないのを知らずに、該当部分の設計書しか読んでいなかった時なんかに、「項目 A と B を取り違えています」などと指摘される感じだ。

文字数・行数が長くなっていいから、スラスラ読めるコードを書こう

つーワケで長々愚痴ってきた説明してきた「説明変数」と、分かりやすコードの価値についてだが、いかがだったろうか。

コードは書く時間より読まれる時間の方が長い。誤解なく読んでもらわないと、バグを埋め込んでしまうリスクがある。

クラス・関数・引数・変数の命名を、より説明的にするだけで、コードが自然言語のようにスラスラ読める読み物になっていき、読み手の認知負荷が軽減できる。読み手が正しく理解できたコードは、ミスなく直せるコードでもあるので、みんながハッピーになる。

どういう命名にしたらいいかハッキリ分からない状態では、本当はコーディングなんてできないはず。そんな中途半端なコードを読まされるのは、書いたコードの動作確認をする5分後の自分、レビュアー、テスター、保守メンバ、改修メンバである。皆が困るようなコードを書く前に、自分の理解を整理してから書き始めよう。

何を作るべきかハッキリしていれば、変数名の文字数が数文字増えても、説明変数を分けるために行数がかさんでも、コーディングの時間は長くなるどころか、むしろ分からないまま書き始めている時より短くなる。タイピング速度はそれを生業にしてるプロなんだから日々鍛えときゃいいし、IDE など文明の利器をフル活用して時短効率化できなくて何がエンジニアだ。頭を使って概念を具体化し、正確に言語化・文書化・コード化してこそエンジニアだ。

命名は横着こいていいポイントじゃない。たとえ時間がかかろうとも一番注意すべきポイントだ。ココさえできていれば、あとは文字を打つだけ、とすらいえる。作業の仕方・考え方が間違っているから、もっと周りの読み手のこと、コードを書いた後、リリース後の運用保守フェーズで自分のコードがどのように扱われるのかを考えて、逆算してやるべきことをやっておこう。