TL;DR
- 命名はプログラミングの中で重要かつ難しいタスクである
- コメントが出てきた時点で一考しよう. リファクタリングのチャンス
- 抽象化は慎重に
- 仕事を小さくすることで命名は比較的簡単にできる
- コードに残らない事情はコメントで表明する
あるslackの雑談から始まった
僕が属しているコミュニティであった雑談から.
ういろう [11:26 AM] 結構、コメント書くって難しいよね・・・ コメントと変数名は、プログラム書くより難しいんじゃないかって思ってる。 抽象メソッドにどんなコメントを書くかーみたいなことも今考え中。 okashoi [11:41 AM] 抽象メソッドなら入出力だけわかればいいんじゃないのかな > コメントと変数名は、プログラム書くより難しいんじゃないかって思ってる。 わかる。プログラミングするときに最も脳のリソースを消費するのは命名かなっておもってる(やや大げさ) ういろう [11:59 AM] > プログラミングするときに最も脳のリソースを消費するのは命名かなっておもってる(やや大げさ) わかる・・・ある程度テンプレートがあるとはいえ、 正解がコード書くより不明瞭で・・・ リーダブルコード読んでも、までこう書くといい!みたいなのがないから難しい (edited)
そう!
プログラミングにおいて 命名はかなり重要 & 難しいタスク だと思う!
今回は, 名前付けやコメントを書くときに自分がどのようにやっているか,
どのようにして名前付けをしやすいコードの書き方をしているか書いていこうと思う.
そもそも一般的に名前付けは難しいのか
ここは, プリンシプル オブ プログラミング から引用する
2.7 名前重要 コードで命名は最重要課題プログラミングにおいて、「命名」を最重要課題として認識し、慎重に取り組むようにしましょう。 「名前を付ける行為」、そして、それを経て生まれた「名前そのもの」、両方に重要な価値があります。 ・名前を付ける行為 適切な名前を付けられたということは、その要素が正しく理解されて、正しく設計されているということです。 逆に、ふさわしい名前が付けられないということは、その要素が果たすべき役割について、プログラマ自身が十分理解できていないということです。 適切な名前を付けることができたら、その設計の大部分が完成したと言っても過言ではありません。 ・名前そのもの 名前は、コードを通じて、プログラマ同士がコミュニケーションするための最大の場となります。 コードを書いた人と読む人が同時にその場にいて、「リアルタイムの会話」ができることは稀です。 たいていは「コードを通じての会話」となるので、名前が適切でないと、コード上の会話は成り立ちません。 このリアルタイムでないコミュニケーションを円滑にするため、名前には最大限の配慮がなされるべきです。
特に大事な部分をpick upする.
コードで命名は最重要課題プログラミングにおいて、「命名」を最重要課題として認識し、慎重に取り組むようにしましょう。
つまり, プログラミングでもっとも頭を使うのは「命名」と考えていいだろう.
上記に記したようにやはり命名は難しいのだ.
ではなぜ命名が難しいのか
ではなぜ命名が難しいのだろうか.
ここに関しては色々意見はあるのだろうが,
僕自身が考える一番の理由は
命名は設計の出来にかなり影響されるから
だと思っている.
...何が言いたいかというと...
名前付けが難しい = 設計がよくない
つまり, プログラマが名前をつけるのが難しい!!!と感じた瞬間に,
設計を見直すべきだよー というサインをコードが教えてくれているのだ.
適切な名前を付けられたということは、その要素が正しく理解されて、正しく設計されているということです。 逆に、ふさわしい名前が付けられないということは、その要素が果たすべき役割について、プログラマ自身が十分理解できていないということです。 適切な名前を付けることができたら、その設計の大部分が完成したと言っても過言ではありません。
例を見て考えてみる
命名(コメントも同様に)が難しいことはわかってきた.
じゃ, その難しさに僕たちはどのように立ち向かっていくべきか考えて行こう.
ということで雑談の続きを例に進めていく.
ちなみに, webアプリケーションの話をしていることに留意して欲しい.
コードはphpで書いていく.
ういろう [11:53 AM] ふむふむ。 ただ、変数名が意味を持ってしまうから、難しいところ。 最低限のコメントあったほうが良いのかなとか。 下のコードのようにやるとやり過ぎなのかなとか。
/*
* htmlを表示させる
*/
abstract class exClass
{
abstract public function show();
}
/*
* データベースからデータを取得して、htmlを表示させる
*/
abstract class exClass
{
abstract public function show();
}
ういろう [11:58 AM] > プログラミングするときに最も脳のリソースを消費するのは命名かなっておもってる(やや大げさ) わかる・・・ある程度テンプレートがあるとはいえ、 正解がコード書くより不明瞭で・・・ リーダブルコード 読んでも、こう書くといい!みたいなのがないから難しい
ここまで読んでみるとなんとなく Controllerっぽい挙動を意図したmethodについて話していることがわかる. 読み解ける情報を列挙して行こう.
- showというメソッドでhtmlを表示したい
- showメソッドに対して
htmlを表示するという役割をコメントで表現しようとしてる - 抽象クラスとして宣言することでいろんなところで使いたい
1つずつ見て行こう.
1. showというメソッドでhtmlを表示したい
まずは, メソッドの役割を明確にしたい.
名前付けにおいてメソッドや変数の役割を日本語で明確に定義する工程は有効である.
なぜならば, 名前付けをする際には名前をつける対象をよく知る必要があるからだ.
日本人が生まれたての女の子に太郎 と名付けないように, 名付けられる対象が持つ特徴や置かれたプロダクトの文化, コードの文脈を理解して初めて名前をつけることができる.
先ほどのコメント htmlを表示するという表現は不明瞭さを含んでいる表現である.
viewとしてhtmlそのものを表示する役割を担っているのか,
controller内の1つのActionとしてhtml形式でresponseを返すのか,
CLIにhtmlを表示するdumpメソッドなのか(多分こんなことはしないけど),
メソッドが担う役割を明確に示す必要がある.
ここでは, controller内の1つのActionとしてhtml形式でresponseを返すという文脈で話を進めていこう.
一般的にはController内のエンドポイントをActionと表現するため, showActionという名前付けが妥当に思える. (もちろん, プロダクトの文化や一貫性にもよるのでshowという名前もなくはないし, Controllerの中で宣言されていればそれはきっとActionだろう)
showAction() のように明確にhttp responseを返しそうな名前付けをすると役割が明確になる.
明確に役割を定義することは名前付けを手助けするのである.
2. showメソッドに対して"htmlを表示する"という役割をコメントで表現しようとしてる
showActionのような名前付けを行なった結果, http responseが返ってきそうなメソッドとして扱えるようになる.
では, もう一度コメントを見てみよう.
- htmlを表示させる
- データベースからデータを取得して、htmlを表示させる
どちらもhtmlを表示させることを意識したコメントである.
showActionという名前からhtmlをresponseとして返してくることは容易に想像できると思う. 名前が自分の役割をはっきりと表明していれば, メソッドに対してコメントを書く必要はないのである.
業務委託でいらっしゃっている@t_wadaさんが
コメントが出てきた時点でリファクタリングチャンスとおっしゃっていた.
コメントを書いた時点で一度立ち止まり設計や命名を考え直すべきだろう.
下にサンプルコードを書いてみるが, おそらく読みやすさはそれほど変わらないと思う.
abstract public function showAction(); /* * htmlを表示させる */ abstract public function showAction();
実装に語らせる
あとは, 実装に語らせるという方法も取れる.
これは変数の命名で役割を明確にする方法である.
例えば, showActionが返しうる値がhtml, json, arrayの3種類だったとしよう.
この場合, 以下のように実装すればコメントは不要となる.
// jsonを返すパターン
public function showAction()
{
$json = createJsonResponse;
return $json;
}
// htmlそのものを返すパターン
// 多分あんまりない
public function showAction()
{
$html = createHtmlResponse;
return $html;
}
// テンプレートにレンダリングするパラメタを返すパターン
public function showAction()
{
$response = [];
$response = createResponseParams();
return $response;
}
リーダブルコードにもあるが, 変数名は短いコメントと考えておくことが大切である.
メソッドと変数に対して正確に役割を示す端的な名前をつけておくことで, 名前に役割を説明させることができるのである.
これによって必然的にコメントは減っていく. コメントを減らすことでメンテナンスコストが下がるのも嬉しい部分である.
3. 抽象クラスとして宣言することでいろんなところで使いたい
抽象化(abstract), 実はこれも難しい.
これは命名とは別の話であるが,
実際早すぎる抽象化は命名を難しくすることもあるので触れておこう.
DRY (Don't Repeat Yourself), 同じものを繰り返すなという有名な言葉がある.
これに素直に従うと抽象化を急いでしまう.
実はこれも慎重に行うべきで, 早すぎる抽象化は負債化することがある.
YAGNI You ain't gonna need it: 実際に必要となるまでは追加しないとも呼ばれるが, 同じようなパターンが出現していない時点で想像で抽象化を行うと使われず, いじりにくいコードとなり負債化する.
同じような実装が3回,4回登場した時点で抽象化を始めても遅くはないのだ.
例えば, 2. showメソッドに対して"htmlを表示する"という役割をコメントで表現しようとしてるで示した例のように同じような役割で少しだけ実装が違うようなものが3回, 4出現した時に初めて抽象化を考えればいい.
@t_wadaさんとペアプロした時におっしゃっていた言葉を引用しよう.
共通化は2out, 3outしてから考えていい. 1回目で共通化しようとしても将来生まれる需要と食い違って使われないことが多い. 2,3回, いや, 3,4回くらい同じコードを見てから共通化しても遅くない.
設計として正しくない場所に抽象化した概念を置いてしまった場合,
無理に抽象概念を使いまわそうとすると設計に歪みが生まれる.
これによって, 役割が複雑になってしまい命名が難しくなる.
このような副作用もあるため抽象化は慎重になるべきである.
命名の難しさへの防衛術
つらつらと述べてきたように, 変数やメソッドの名前がうまく決まらない(コメントが必要な程度に複雑さを持っている)場合は変数, メソッドの場所がおかしい/役割が多いことを示している. なのでコメント書いた時点で一旦考えてよいとおもう.
では, どのようにしてその複雑さや難しさを回避していくか.
3つの手順を踏むことでその難しさを軽減することができる. ここからは, コードで示しながら進めていく.
これは大きなメソッド(多分こういったメソッドにコメントが生えがち)をリファクタリングするときに使える手法である.
1: まずはやりたいメソッドをべたっと1つのメソッドに書く(もしくは最初からある)
class exController
{
// requestを受けて新規データを作成する
public function createAction($request)
{
process1
process2
process3
process4
process5
process6
process7
process8
return process9
}
}
2: メソッドの処理を役割ごとにブロック化してそれぞれにコメントをつけていく
class exController
{
// requestを受けて新規データを作成する
public function createAction($request)
{
// request parse する
process1
// requestのvalidation
process2
process3
process4
// requestからentity作る
process5
process6
// ORMにentity登録してdbにinsert
process7
process8
// response返す
return process9
}
}
3: コメントごとにmethodに切り出す
class exController
{
public function createAction($request)
{
$req = parseRequest($request)
validateRequest($req)
$entity = createEntityByRequest($req)
insertEntity($entity)
return process9
}
}
public function parseRequest($request)
{
return process1
}
public function validateRequest($request)
{
process2
process3
return process4
}
public function createEntityByRequest($request)
{
process5
return process6
}
public function insertEntity($entity)
{
process7
return process8
}
実コードでこのやり方を実践すると, きっと初期状態よりもずっと追いやすく読みやすいコードになるだろう(この例だと初期状態が複雑に見えないためメリットを感じづらいが, やってみると効果が実感できると思う)
上記のようなフローでコード書くと, 以下のような良さがある
- まずべたっと1枚岩でコードを書くことで処理の流れをそのまま表現でき書きやすい
- 分割した後はメソッドの役割がシンプルになり命名しやすい
- メソッドや変数の仕事が少なくなるため役割がシンプルになる
- その結果, 副次的に命名がしやすくなる
- 各メソッド, 変数の役割や仕事が小さくなるため状態管理が楽, かつ, 汎用的になる
- メソッドの使い回しが効くようになる
- 変数のスコープが小さく, 状態管理が楽なのでテストしやすい
- 参照透過性も守りやすい
コメントを書かなければならない場合もある
コメントを書かなければいけない場合もある.
これはコードでは表現できない事情や背景がある場合である.
これらはコメントで残さなければ後に残っていかないため, コメントに残すべきである.
パフォーマンス上の制約でしぶしぶ選んだ選択や事業的な理由など, コードそのものでは示せないものはコメントに残すべきである.
裏返せば, コードを読めばわかることはコメントに書くべきではない ということを示している.
コメントを書いた時点で立ち止まる癖は付けても良いだろう
まとめ
- 命名はプログラミングの中で重要かつ難しいタスクである
- コメントが出てきた時点で一考しよう. リファクタリングのチャンス
- 抽象化は慎重に
- 仕事を小さくすることで命名は比較的簡単にできる
- コードに残らない事情はコメントで表明する