以下はThe Mistakes I Made As a Beginner Programmerの日本語訳です。
The Mistakes I Made As a Beginner Programmer
初心者プログラマが犯しがちな間違いと、それらを特定し、避けるための習慣を学ぶ方法。
まず最初に言っておくことがあります。
この記事は、誤りを犯すことを悪いと糾弾するために作成されたものではありません。
むしろ貴方が誤りに自ら気付き、あるいはその兆候を見いだし、それらを避けられるようにするために書かれたものです。
私は過去これらの誤りを犯し、それぞれから学びを得てきました。
今ではこれらを避けるようなコーディングを習慣付けるようにしています。
貴方もそうしましょう。
紹介は順不同です。
1) 設計せずに実装する
高品質なコンテンツは、一般的には容易に作成できるものではありません。
それには慎重な思考と研究が必要です。
高品質なプログラムももちろん例外ではありません。
良いプログラムは、適切なフローに従って書かれるものです。
思考、研究、計画、実装、検証、修正。
残念ながら、これを一言で表す適当な語句がありません。
それぞれのプロセスにどれだけのウェイトを置くか、適切な重み付けを検討する習慣を作る必要があります。
私が初心者だった頃に犯した最大の間違いは、思考や研究をせずいきなりコードを書いたことでした。
これは小さなスダンドアローンアプリには有効な手段かもしれませんが、大規模なアプリには多大な悪影響をもたらします。
考える前に話すことで後悔することがあるかもしれない、と考えるの同様に、考える前にコーディングすることで後悔することがあるかもしれない、と考える必要があります。
コーディングも考えを伝える手段のひとつです。
怒ってるときは、口を開く前に10数えよ。激おこであれば100だ。
- Thomas Jefferson.
私が言い換えるとこうなります。
コードを書いてるときは、リファクタする前に10数えよ。テストを書いてないなら100だ。
- Samer Buna
プログラミングとは、主に既存のコードを読むことです。
そして必要な機能と現在の実装の乖離を調査し、どのようにすれば最小限のテストでその差異を埋められるかを設計することです。
実際のコードの記述は、おそらくプロセス全体の10%程度しかありません。
プログラミングとはコードを書くことだ、とは思わないでください。
プログラミングは、成長を必要とするロジカルな創造性です。
2) 実装する前に設計しすぎる
コードを書く前に計画を立てるのは良いことです。はい。
しかし、あまりに多くをしすぎると却って悪くなります。
ただの水でも量が過ぎれば毒になるように。
完璧な設計を作り上げようとしてはいけません。
プログラミングの世界にそれは存在しません。
実装を始めるのに必要で十分なレベルの設計を探してください。
実際、設計はよく変わるものです。
適切な設計はコードの構造をクリアにするために必要なものです。
しかし、多すぎる設計は、単に時間の無駄です。
設計は少しずつ行った方がよい、ということです。
全ての機能を一度に設計することは、単純に禁止すべきです。
それはウォーターフォールと呼ばれ、システムを順番にひとつひとつ終わらせていく設計です。
その手法ではいったいどれだけの設計が必要になるでしょう。
ほとんどのソフトウェアプロジェクトでは、ウォーターフォール設計はうまくいきません。
複雑なものになるほどアジャイルでしか対応できなくなります。
プログラム開発は、変化に敏感である必要があります。
ウォーターフォール設計時点では無かった機能を実装することがあるでしょう。
ウォーターフォール設計時点では考慮していなかった理由のために機能を削除することがあるでしょう。
バグは修正し、変化に適応する必要があります。
すなわちアジャイルである必要があります。
ただし、次に実装する予定のいくつかの機能については常に設計してください。
少なすぎる設計も、多すぎる設計も、あなたのコードの品質を損ないます。
そして低品質なコードは、あなた自体の品質に繋がります。
3) 品質管理の過小評価
コードを書くときに優先することをひとつだけ挙げるとするならば、それは読みやすさです。
読みにくいコードはガラクタです。
コードはまたリサイクル可能でなければなりません。
コードの品質管理の重要性を過小評価しないでください。
コーディングは実装を伝える手段だと考えてください。
コーダーの主な仕事は、作業中のソリューションの実装を伝えることです。
プログラミングに関する、私のお気に入りのフレーズのひとつを紹介します。
コードを書くときは、メンテナはあなたの住所を知っているサイコパスである、と肝に銘じてコーディングしなさい。 - John Woods
John、素敵なアドバイスをありがとう。
小さなことですら問題です。
たとえば、インデントと大文字小文字が正しくないだけで、あなたにはコーディングの資格がないとみなされます。
tHIS is
WAY MORE important
than
you think
他にすぐわかる不味い点は、長い行です。
80文字を超えるような行はより読みづらくなります。
if
ブロックを見やすくするため、複数の条件式を1行にまとめて書きたくなるかもしれません。
そのようなことをしてはいけません。
80文字の制限を超えてはいけません。
このような単純な問題の多くは、Linterや整形ツールで簡単に修正可能です。
JavaScriptであればESLintとPrettierというふたつの優れたツールが存在します。
常にそれらを使うようにしてください。
コード品質については、いくつもの地雷が存在します。
関数の行数が多すぎる
長いコードは、常に個別にテスト可能な小さな単位に分割する必要があります。
個人的には10行以上の関数は長すぎると思っていますが、これはあくまで目安です。
二重否定を使う
使ってはいけません。
二重否定を使うのは非常に悪くなくないです。
短すぎる、汎用的な、データ型を使った命名
変数名には説明的で、曖昧ではない名前を付けます。
コンピュータサイエンスで難しいことはたった二つだけだ。キャッシュ削除と命名だ。 - Phil Karlton
マジックナンバーのハードコーディング
文字列や数値で固定のプリミティブ値を設定したい場合は、その値を定数に入れ、適切な名前を付けます。
const answerToLifeTheUniverseAndEverything = 42;
問題を回避する
ショートカットや回避策を弄して問題から逃げてはいけません。
現実に直面しましょう。
長いコードが良いと考える
大抵の場合は短いコードの方がよいです。
コードが読みやすくなるのであれば冗長なコードを選びましょう。
コードを短くするために、技巧を凝らしたワンライナーや、三項演算子のネストなどを書いたりしないでください。
とはいえ、必要のないときにまで意図的にコードを長くする必要もありません。
不要なコードを削除することは、プログラムに対して行える最もよい改善点です。
プログラムの進捗を行数で計るのは、飛行機建造の進捗を重さで量るようなものだ。 - Bill Gates
条件付きロジックの多用
条件付きロジックが必要だと思われているケースの大半では、条件付きロジックが不要です。
代替案も確認し、最も読みやすいと思われる選択肢を選びましょう。
明らかに速度が異ならないかぎり、パフォーマンスを最適化する必要はありません。
またヨーダ記法や条件式中での値代入なども避けましょう。
4) 最初の解決策に飛びつく
私がプログラムを始めたころ、提示された問題に対して解決策を見付けたら即座にそれに飛びついていたことを覚えています。
最初の解決策の複雑さの程度について考える前に実装を始め、そして必然的に失敗に結びついていました。
最初の解決策は魅力的かもしれませんが、よくよく考えてみると大抵はより優れた解決策を見付けられます。
問題に対して複数の解決策を考えつくことができないようであれば、それはおそらく問題を完全には理解できていないということです。
プログラマーとしてのあなたの仕事は、問題の解決策を見つけることではありません。
最もシンプルな問題の解決策を見つけることです。
シンプルとは、解決策が正しく適切に機能し、その上で読みやすく、理解しやすく、保守しやすいということです。
ソフトウェアの設計にはふたつの方法がある。ひとつめは、可能なかぎりシンプルにして明らかに欠陥がないようにすること。もうひとつは、可能なかぎり複雑にして明らかな欠陥がないようにすることだ。 - C.A.R. Hoare
5) 諦めない
私が最も頻繁に犯した間違いが、『最初の解決策が最も簡単な解決策ではないことに気付いた後も、最初の解決策に固執する』ことでした。
諦めない精神が悪い形で出ています。
諦めない精神は、たいていの活動においてはよい心がけですが、プログラミングに適用すべきではありません。
ことプログラミングにおいては、正しい心は早々に死滅し、頻繁に失敗します。
解決策に疑問を覚えたなら、一度それを投げ捨てて問題を再考してみましょう。
その解決策に、それまでどれだけの投資をしていたとしてもです。
gitのようなソース管理ツールは、様々な解決策を使い分けて試してみるのに適しています。
そのコードにどれだけの努力を払ったかは、コードの品質には全く関係ありません。
悪いコードは排除する必要があります。
6) ググらない
問題を解決しようとする際に、最初にすべきだったことをしなかったがために多大な時間を浪費したことがたくさんあります。
よほど最先端の技術でも使用していないかぎり、あなたが出会った問題は、大抵の場合誰かが既に同じ問題に遭遇して解決したことのあるものです。
つまり最初にググれということです。
時には、あなたが問題だと思っていたことは別に問題ではないということもGoogleは明らかにしてくれます。
あなたに必要なのは、問題を修正することではなく、むしろそれを受け入れることです。
問題を解決するのに必要なことが全てわかっている必要はありません。
Googleは驚くべき解決策を持ってきてくれるでしょう。
ただし、Googleを使うときは念頭に置いてください。
初心者にありがちな行動のひとつが、出てきたコードを読まずにそのままコピーして使用することです。
たとえ問題を正しく解決するコードだったとしても、理解していないコードは決して使用しないでください。
クリエイティブな人間として最も危険な考えは、自分がやっていることは自分が知っていることだ、と思い込むことです。 — Bret Victor
7) カプセル化しない
ポイントは、オブジェクト指向パラダイムを使用するという意味ではないことです。
技術にかかわらずカプセル化の考え方は常に有用です。
カプセル化を行わないシステムは、しばしば保守が困難になります。
アプリケーションには、ある機能を処理する場所は一カ所しかありません。
それは通常、ひとつのオブジェクトの責任です。
そのオブジェクトが公開するのは、外からそのオブジェクトを使うのに必要な最低限の情報だけでなければなりません。
これは機密を保つためではなく、アプリケーションの各部分の依存関係を減らすというコンセプトに基づくものです。
これらの規則に従うことで、どこか離れた場所で何かが動かなくなるのではないかと心配することなく、クラス、オブジェクト、メソッド内部を安全に変更することが可能になります。
クラスは、関連するロジックとステートを集めた概念的集合の単位で作ります。
By class, I mean a blueprint template.
This can be an actual Class object or a Function object.
モジュール、もしくはパッケージとして識別されることもあります。
ロジッククラスでは、ひとつの独立したタスクにはひとつのメソッドが必要です。
メソッドはひとつのことだけを行い、それを正しく処理しなければなりません。
同じようなクラスは同じメソッドを持っているべきです。
初心者プログラマだった頃、私はどのようにクラスを分けるべきかの概念的集合がよくわからず、何が独立したタスクなのかを切り分けることもできませんでした。
それぞれの関係が特にないような雑多なコードが詰め込まれた"Util"クラスができあがってくれば、それは初心者である兆候です。
一カ所に簡単な変更を加えたところ、別のところに問題が波及し、何カ所も修正を行わなければならなかった場合、それもまた初心者コードの特徴です。
クラスにメソッドを追加する、あるいはメソッドに機能を追加する前に、考える時間を取ってください。
考えを後回しにしたり、後でリファクタリングすればいいやなどと考えたりしないでください。
ここが正しい道への第一歩です。
よい考え方は、コードは高凝集で低結合なものにするということです。
「高凝集で低結合」とは、関連するコードはひとつのクラスにまとめて記述し、異なるクラス間での依存関係を減らす、という意味を表すファンシーな用語です。
8) 不要な拡張性を盛り込む
現在の解決策を超えた方法を考えることはしばしば魅力的です。
あなたが書いたあらゆる行で、「もしここで○○だったら」という考えがよぎるかもしれません。
これはエッジケースのテストについて考えるときにはよいことですが、まだ実装されていない対象について対応するのは間違いです。
頭をよぎった「もしここで○○だったら」が、両者のどちらであるかをしっかり分類する必要があります。
今日必要の無いコードは今日書かないでください。
決まっていない事象を織り込まないでください。
成長のための成長は癌細胞だ - Edward Abbey
9) 正しいデータ構造を使わない
コードレビューの際、初心者プログラマはアルゴリズムに重点を置きがちです。
適切なアルゴリズムを特定し、必要に応じてそれらを使用するのはよいことですが、それだけでは天才プログラマにはなれないでしょう。
使用している言語に用意されている様々なデータ構造について、その長所と短所を覚えておくと、より良い開発者になれるはずです。
不適切なデータ構造を使うことは、つまり『ここに初心者がいますよ』と触れ回るに等しい行為です。
この記事ではデータ構造の詳細にまでは立ち入りませんが、簡単な例を幾つか挙げておきます。
Using lists (arrays) instead of maps (objects) to manage records
最もよくあるデータ構造選択の誤りは、複数のレコードをmapではなくlistで管理することです。
はい、レコードの管理はmapを使わなければなりません。
以下は各レコードにそのレコードを特定するための一意のキーが存在する場合についての話になります。
スカラー値にlistを使っても問題なく、特に値をpushして使っていた場合にはより良い選択になります。
JavaScriptでは最も一般的なlistはarrayで、最も一般的なmapはobjectです(最近はmapもあります)。
レコードを管理するためにlistを使うのは誤りです。
これが重要である理由は、識別子を使ってレコードを検索する際に、mapはlistより遙かに高速だということです。
実際に目に見える影響が出てくるのはコレクションが巨大になってきたときだけですが、それだけでも固執するには十分な理由です。
Not Using Stacks
繰り返しを必要とするコードを書く場合、単純に再帰を使うのは簡単な選択です。
しかし、再帰コードを最適化するのは非常に難しいです。
特にシングルスレッド環境であればなおさらです。
再帰関数の最適化は、対象によって難易度が著しく異なります。
たとえば2値以上の値を返すコードの最適化は、返り値がひとつだけの関数よりも遙かに難しくなります。
初心者が見落としがちなのが、再帰のかわりに使えるデータ構造があるということです。
それはスタックと呼ばれます。
関数を呼び出すかわりにスタックに積んで、用意が出てきたらpopします。
10) コードを悪化させる
このように乱雑な部屋を与えられたと想像してください。
この部屋に新たなアイテムを設置するよう求められました。
部屋は既にじゅうぶん乱雑なので、適当に置いてもかまわないと考えるかもしれません。
その作業は数秒で完了することでしょう。
乱雑なコードでそれをしてはいけません。
決して悪化させてはいけません。
少しでいいから、作業を開始したときよりもコードを綺麗にしましょう。
上の部屋に行うべき正しいことは、新しいアイテムを置くために必要な位置を整頓することです。
たとえば、アイテムが衣服であった場合、まずはクローゼットへの道を片付ける必要があります。
それこそが、あなたが仕事を正しく行うことの一部です。
コードをしばしば悪化させる例を、いくつか挙げておきます。
Duplicating code
コードをコピペして一部の行を変更する行為は重複コードと呼ばれ、コードは純粋に悪化します。
汚れた部屋の例で言うと、高さが調整可能な椅子を導入するのではなく、高さの異なる椅子を導入するようなものです。
できるかぎり抽象的に使うように心がけてください。
Not using configuration files
環境や時間帯によって異なる値を使用したい場合、その値は設定ファイルに書き出しましょう。
コード内の複数箇所でひとつの値を使用したい場合、その値は設定ファイルに書き出しましょう。
コードに新しい値を導入する際は、その値は設定ファイルに書き出すべきかどうかを考えてください。
なお、大抵の場合答えはイエスです。
Using unnecessary conditional statements and temporary variables
全てのif文は少なくとも2回テストする必要がある分岐です。
可読性を下げることなく分岐を避けられるのであれば、そうすべきです。
新たな関数を作るかわりに、関数に分岐ロジックを導入した際によく問題になります。
if文や新たな関数が必要になるたびに、その変更は適切か、もっと異なる次元で対応すべきか、自問してください。
function isOdd(number) {
if (number % 2 === 1) {
return true;
} else {
return false;
}
}
isOdd
はいくつか問題がありますが、最も大きな問題はどこでしょうか。
if文は明らかに不要で、コードは以下と同じです。
function isOdd(number) {
return (number % 2 === 1);
};
11) 意味の無いコメントを書く
できればコメントは書かないようにしたいですが難しいところです。
ほとんどのコメントは、コード内の要素名を適切に置き換えることで排除できます。
// This function sums only odd numbers in an array
const sum = (val) => {
return val.reduce((a, b) => {
if (b % 2 === 1) { // If the current number is even
a+=b; // Add current number to accumulator
}
return a; // The accumulator
}, 0);
};
このコードは、以下のようにコメント無しで書くことができます。
const sumOddValues = (array) => {
return array.reduce((accumulator, currentNumber) => {
if (isOdd(currentNumber)) {
return accumulator + currentNumber;
}
return accumulator;
}, 0);
};
単に関数名や引数名を適切に付けるだけで、大抵のコメントは不要になります。
コメントを書く前に思い出してください。
しかし、どうしてもコメントを書かなければならない状況に陥ることもよくあります。
それは、このコードは何であるか(WHAT)ではなく、このコードは何故そうなのか(WHY)を説明しなければならないときです。
コードにWHATコメントを書くことが求められている場合でも、明らかに明白なことは書かないでください。
// create a variable and initialize it to 0
let sum = 0;
// Loop over array
array.forEach(
// For each number in the array
(number) => {
// Add the current number to the sum variable
sum += number;
}
);
これらはコードにノイズを加えるだけの、全く役に立たないコメントです。
このようなプログラマになってはいけません。
このようなコードを受理してはいけません。
対処が可能ならコメントを削除してください。
部下がこのようなコメントを書いてきたら即座に解雇してください。
12) テストを書かない
要点を簡潔に述べましょう。
あなたが自分を、テストを書かずとも思考をそのままコードに落とし込める腕を持っている熟練プログラマである、と考えているのであれば、あなたは初心者です。
テストコードを書いていない場合は、それ以外の方法でプログラムを手動テストすることが多いでしょう。
Webアプリを作っているのであれば、コードを数行書くごとに画面を再描画して確認します。
私もそれは行います。
コードを手動テストするのはおかしなことではありません。
ただし、それと同時にコードを自動テストする方法についても考えておかねばなりません。
手動テストが正常に終わり、コードエディタに戻り、新たなコードを書き、再び手動テストを行う、このように全く同じ動作を行うのであれば同じ動作を自動的に実行するコードを記述しないわけにはいきません。
あなたは人間です。
すなわち、あなたは前回行ったテストの内容を忘れます。
そのような繰り返しはコンピュータにやらせましょう。
可能であれば、コード本体を書き始める前にコードが満たすべき条件を設計・推測するところから始めるとよいでしょう。
テスト駆動開発 ( TDD ) は伊達ではなく、機能やデザインについて考えることにプラスの影響を与えます。
TDDは全ての人に適しているわけではなく、うまく適用できないプロジェクトも存在しますが、とはいえ一部でも適用できるならば導入すべき手法です。
13) コードが動いてるなら、コードは正しい
奇数だけを足し合わせるsumOddValues
関数を見てみましょう。
何か問題はあるでしょうか。
const sumOddValues = (array) => {
return array.reduce((accumulator, currentNumber) => {
if (currentNumber % 2 === 1) {
return accumulator + currentNumber;
}
return accumulator;
});
};
console.assert(
sumOddValues([1, 2, 3, 4, 5]) === 9
);
assert
は問題なく動作します。よかったよかった。
このコードは未完成です。
このコードはサンプルコードを含めたいくつかのテストを通しますが、それ以上に問題があります。
例を挙げてみましょう。
Problem #1
引数が空の時の処理がありません。
この関数を引数なしで呼び出すとエラーが発生します。
TypeError: Cannot read property 'reduce' of undefined.
これは2つの点で良くないコードです。
・関数の使用者は、その実装の詳細を知りません。
・このエラーメッセージは使用者にとって役立ちません。
この関数は機能しませんでしたが、たとえば次のように適切な例外を出したりすることで、その理由が使用者が誤って使ったことだとわかるようになります。
TypeError: Cannot execute function for empty list.
しかし、おそらくはエラーを投げるかわりに0を返すような設計にした方が適切かもしれません。
何れにせよ、元のままではなく何らかの改修を行う必要はあるでしょう。
Problem #2
不正な入力への対策がありません。
配列ではなく文字列、数値、Objectなどが突っ込まれたときにはどうなるでしょうか。
sumOddValues(42);
TypeError: array.reduce is not a function
array.reduce
は間違いなく関数なので、このエラーメッセージはとても残念です。
引き数名にarray
と名前を付けたので、引数は何であれarray
という名前になってしまいます。
エラーメッセージが意味するところは、42.reduce
は関数ではないということです。
このエラーメッセージは明らかに混乱を助長するでしょう。
以下のようなメッセージを出した方が有用です。
TypeError: 42 is not an array, dude.
問題点1と2はエッジケースと呼ばれます。
エッジケースは対応方法がほとんどパターン化されているので、対応方法について考慮を必要とするようなエッジケースはほとんどありません。
以下の例はどうでしょう。
sumOddValues([1, 2, 3, 4, 5, -13]) // => still 9
-13は奇数であるにもかかわらず、この関数は9を返してきます。
この関数には、この機能が必要ですか?
この入力には例外を出す必要がありますか?
負数を受け入れる必要がありますか?
今のように負数を無視するだけでいいですか?
そうであれば関数名はsumPositiveOddNumbers
のほうが適切でしょう。
上記例ではどのように仕様を決めるかは簡単でした。
重要なポイントは、その仕様を明文化するためのテストケースを書いていなかった場合、将来の保守担当者は負数を無視することが意図的なものかバグなのかわからないことです。
それは仕様です。 - 詠み人知らず
Problem #3
有効な全てのケースがテストされていません。
この関数には、処理が正しく行われない非常に単純なエッジケースが存在します。
sumOddValues([2, 1, 3, 4, 5]) // => 11
2は奇数ではないのに結果に含まれてしまっています。
理由は簡単で、reduce
に第二引数を渡すとaccumulator
の初期値として扱います。
第二引数を渡さない場合、reduce
はコレクションの最初の値をaccumulator
の初期値にします。
これによって、sumOddValues
の結果にはコレクションの1番目の値が常に含まれてしまいます。
コードを書いたときに、これらの値をチェックするテストを書いていれば、問題をすぐに発見することができたでしょう。
テストを最小限しか書かない、エッジケースのテストを書かない、などの兆候は初心者の証です。
14) 既存コードを疑わない
あなたが常にソロで働いているスーパースターでもないかぎり、品質のよくないコードに出会わないということはありません。
初心者は出会ったコードの品質を気にせず、正しく動くのだから良いコードだと認識し、自分の知識に取り入れます。
さらに悪いこと、彼らはそれらが良いコードであると思っているため、至る所でその悪いコードを量産するようになります。
一見非常によくないコードのように見えるものもありますが、何らかの特別な理由によってあえてそのように書かれているコードも時にはあります。
そのような場合は、そのコードがどうしてそう書かれているのかを解説するコメントが付いていることが、よいコードの見分け方になります。
初心者であるならば、理解できないうえに解説も書かれていないコードは悪いコードだ、と考えておくとよいでしょう。
それらについて質問し、git blame
しましょう。
そのコードの作者が既にいない、あるいは内容を覚えていない場合は、そのコードを調査し、中身を理解しましょう。
コードを完全に把握できたときにのみ、そのコードについての善し悪しを判断しましょう。
理解する前に仮定を当て推量してはいけません。
15) ベストプラクティスに拘泥する
"ベストプラクティス"という言葉は有害だと私は考えています。
ベストプラクティスはつまり、これ以上研究する余地はない、疑問を差し挟んではいけない、ということです。
ベストプラクティスなど存在しません。
現時点で、そのプログラミング言語に対しての"グッドプラクティス"が存在するだけです。
かつてベストプラクティスとされていた文法のいくつかは、現在ではバッドプラクティスだと認定されています。
十分に時間をかければ、常によりよい方針を見つけることができるでしょう。
ベストプラクティスにこだわるのをやめ、ベストを尽くすことに集中しましょう。
どこかでそうしろというのを見たから、誰かがそうしているのを見たから、誰かにそう言われたから、だからそうする、ということはしないでください。
これには、私がこの記事で示している全てのアドバイスも含まれています。
全てを疑い、定石に挑戦し、全ての選択肢を調べ、意義のある意思決定を行いましょう。
16) 最適化に拘泥する
時期尚早な最適化は、プログラミングにおける諸悪の根源だ - Donald Knuth
ドナルド・クヌースがこの発言をして以来、プログラミングは大きく変わりましたが、この発言の重要性は変わっていません。
ひとつ覚えておくべきことは、ボトルネックがどこにあるのかを測定するまでは最適化するべきではないということです。
コードを実行する前に最適化するのは時期尚早であり、そもそも最適化が完全に不要である可能性すらあります。
もちろん、新しいコードを導入する際は常に検討しておくべき最適化項目は存在します。
たとえばNode.jsでは、イベントループを溢れさたり、コールスタックをブロックしたりしないことが非常に重要です。
これらは念頭に置いておくべき、真っ先に最適化するべき例です。
実装するコードはコールスタックをブロックするかについては毎回自問してください。
既存のコードに対して、測定を伴わずに行う最適化は有害であり、避けるべきです。
パフォーマンスの向上と引き替えに、予想外のバグが発生する可能性があります。
発生していないパフォーマンス問題の最適化のために時間を無駄にしてはいけません。
17) エンドユーザの視点に立たない
アプリケーションに機能を追加する最も簡単な方法は何でしょう。
自分の立場で考えると、既存のインターフェイスに適合するところを選ぶのが適切でしょう。
その機能がユーザからの情報入力を必要とする場合は、既に存在するフォームに追加します。
その機能がページへのリンクを追加することであれば、既に存在するリンクメニューに追加します。
開発者視点で見てはいけません。
自分がエンドユーザであるという視点で物事を見ましょう。
その機能の使用者は何が必要であるか、ユーザはどのように行動するかを考えましょう。
必要なのは、ユーザがその機能を簡単に見つけて簡単に使える方法であって、簡単に実装する方法ではありません。
18) 適切なツールを使わない
誰もが、プログラミングについてのお気に入りツールを持っています。
いくつかのツールは素晴らしいものであり、いくつかのツールはよくないものですが、ほとんどのツールは、ある分野には強力ですが他の分野にはそれほどでもありません。
ハンマーは釘を壁に打ち込むには良い道具ですが、ねじを回すには最悪の道具です。
どれだけハンマーが好きだったとしても、それをねじ回しとして使ってはいけません。
Amazonのユーザレビューで5.0を取っていたとしてもです。
使うツールを人気で選ぶのは初心者の証です。
問題の理由のひとつは、その仕事により適したツールについて知らないことでしょう。
あなたが今使っているツールは、あなたが今知っている中では最良のツールであるかもしれませんが、しかし決して全てのツールの中で最良というわけではありません。
使えるツールには絶えず手を出し、新しいツールについても使い始めることができるようになっている必要があります。
一部のコーダーは新しいツールの使用を拒否します。
彼らは既存のツールに慣れきっており、新しいツールを習得したがらないでしょう。
彼らの気持ちは理解できますが、しかしその態度は単に間違っています。
あなたは原始的なツールで時間を浪費しながら家を建てることも、良いツールにお金と時間を投資した後に短時間でよりよい家を建築することもできます。
ツールは絶え間なく改善されており、それらについて絶え間なく学習し、使っていく必要があります。
19) コードの問題がデータの問題に派生する
プログラムの重要な仕事のひとつは、何らかの形のデータの管理です。
プログラムは、新しいレコードを追加したり、古いレコードを削除したり、レコードを変更したりするためのインターフェイスです。
プログラムのバグはほんの小さなものであっても、致命的で予測不可能なダメージをデータに与える可能性があります。
全てのデータがバグのあるプログラムを通っている場合、事態はさらに深刻です。
初心者は、コードとデータの関係性が結びつきにくいかもしれません。
その機能は現在動作しておらず、重要でもないので、気にせずバグの入ったコードを使い続けているかもしれません。
問題は、バグの入ったコードが、データの完全性を誰も気がつかないうちに壊してしまっている可能性があることです。
より悪いのは、データに起こったバグに対応せずにコードのバグだけを修正することです。
そのコードは小さなデータの問題を増幅し、ときに回復不可能なレベルにまでデータが破損してしまいます。
これらの問題からデータを守る方法は?
たとえばデータの整合性を検証する複数のレイヤを使用できます。
ひとつのレイヤだけに整合性の担保を頼ってはいけません。
フロントエンド、バックエンド、ネットワーク、およびデータベースのレイヤに検証を入れましょう。
それができないとしても、最低限データベースレベルの制約は使用しなければなりません。
データベースの制約について理解し、テーブルや列を追加するときに使うべき制約は必ず使用しましょう。
NOT NULL
NOT NULL制約は、その列に対してNULL値の設定を禁止します。
アプリケーションがそのフィールドの値を必須としているのであれば、データベースにはNOT NULL制約を入れなければなりません。
UNIQUE
UNIQUE制約は、その列がテーブル全体で重複する値を持つことができない制約です。
たとえばユーザテーブルのユーザ名、メールアドレスなどが指定するのに適切です。
CHECK
CHECK制約は、その式を満たさないかぎりデータを受け入れません。
たとえば、値が0以上100以下でなければならない場合、CHECK制約を使うことでそれを強制できます。
PRIMARY KEY
PRIMARY KEYは、列の値がNULLではなくユニークであることを表します。
おそらくこれは使用しているでしょう。
データベース内の各テーブルには、レコードを識別するためのPRIMARY KEYが必要です。
FOREIGN KEY
FOREIGN KEYは、列の値が、別のテーブルの列、通常はPRIMARY KEYでしょう、と一致しなければならないことを意味します。
新人が犯しがちな、データの整合性に関するもうひとつの問題が、トランザクションという考え方の欠如です。
複数の操作が互いに依存しているデータを変更する場合、それらの操作のひとつが失敗したときに全てを元に戻すために、トランザクションを使う必要があります。
20) 車輪の再発明
ここは難しい点です。
プログラミングはいまだに全てがわかっている分野ではなく、いくつかの車輪には再発明する価値があることもあります。
多くの物事が非常に早く変化し、新しい要素がどんどん流入してきます。
例えば、現在の時間帯に応じて異なる速度で回転する車輪が必要になった場合は、既に存在している車輪をカスタマイズするのではなく、おそらく車輪の再設計が必要になるでしょう。
ただし円形をしていないなど、既存設計に則っていない車輪は、それがどうしても必要でないかぎり再発明しないでください。
多くの選択肢の中から、適切なブランドの車輪を選択することはしばしば困難です。
購入する前にいくつかの選択肢を試してみてください。
ソフトウェアの良いところは、その多くが無料であり、車輪の内部を直接見ることができることです。
車輪の品質は、内部のコード設計を見ることで容易に判断できます。
可能であればオープンソースの車輪を使用してください。
オープンソースは簡単にパッケージを修正することができ、簡単に交換することができ、さらに社内でサポートすることもできます。
なお、車輪が必要な場合は、車を1台購入するのではなく、既に所有している車に車輪だけを取り替えてください。
ひとつふたつの関数を使用するためにライブラリ全体を導入してはいけません。
このことに関する適切な例はJavaScriptのlodashライブラリでしょう。
配列をシャッフルするためには、shuffleメソッドだけをインポートすればよいことで、lodashライブラリ全体をインポートする必要はありません。
21) コードレビューへの向かい方
初心者に見られる兆候のひとつが、コードレビューを批判と捉えることです。
彼らはそれを好まないし、感謝しないし、剰えそれを恐れさえします。
それは間違っています。
そのように感じたとしたら、すぐに向き合う態度を変える必要があります。
全てのコードレビューは学習の機会と考えてください。
それらを歓迎し、認め、それらから学びましょう。
そして最も重要な点はレビュアーに感謝することです。
あなたは永遠にコードの学習者です。
それを認識しなければなりません。
ほとんどのコードレビューは、あなたが知らなかったことを教えてくれます。
学習リソースであると分類しましょう。
時にはレビュアーが誤っていて、あなたが彼らに何かを教える番になることもあるでしょう。
ただし、あなたのコードだけではどちらが良くないか明らかではないのであれば、コードを修正する必要があるかもしれません。
レビュアーに対して何かを教える機会があるならば、それはプログラマーとして最も有益な活動のひとつです。
22) バージョン管理しない
初心者はバージョン管理システムの力を知りません。
ここではGitを対象とします。
バージョン管理は、単に他人のコードに変更を加えてpushするだけのものではありません。
バージョン管理は、つまり歴史です。
コードについて疑念があったときに、そのコードの歴史は疑問を解決してくれる役に立つことでしょう。
従ってコミットメッセージは重要なものになります。
それはあなたの実装が何のために行われたものなのかを表現する手段のひとつです。
コミットはできる限り小規模にすることで、将来のメンテナが、コードが何故そのような状態になっているのかを調査するのに役立ちます。
頻繁に早期に一貫的にコミットし、コミットメッセージは現在形を使用します。
コミットメッセージには要約を、詳しく記載します。
メッセージが数行以上必要になるようであれば、それはコミットが大きすぎるという証なのでrebaseしましょう。
コミットメッセージには不要なものを含めないでください。
たとえば追加変更削除されたファイル名の一覧などは不要です。
それらはコミット自体に入っていて、コマンドで簡単に表示させることが可能なので、単なるノイズでしかありません。
また、ファイルごとに異なるコミットメッセージを付けたいと考える人もいるかもしれませんが、それもおそらくコミットが大きすぎる兆候のひとつでしょう。
バージョン管理は、到達可能性に関するものでもあります。
ある関数の設計や必要性に疑問を感じたときに、その関数が導入されたコミットを見つけて当時の状況を知ることができます。
コミットはまたプログラムにバグが混入されたタイミングを特定するのにも役立ちます。
bisectコマンドを使って、バグが入ってきたコミットをバイナリ検索で特定することができます。
バージョン管理は、変更を公式に取り込むという用途以外でも大活躍します。
ステージング環境の構築、パッチの選択、修正のリセット、棚上げ、コミットの修正、差分の確認、取り消しなどなど、コードフリーに機能豊富なツールが追加されます。
これらを理解し、学習し、使用しましょう。
知っているGitの機能が少ないほど初心者に近いと言えます。
23) グローバル使いすぎ
ここでは関数型プログラミングとそれ以外のパラダイムの差異を話す、わけではありません。
それは別の記事でやります。
ここで話すのは、グローバルは全ての元凶であり、可能な限り避けるべきものである、という事実にすぎません。
万一それが不可能である場合でも、グローバルの使用は必要最小限に抑えなければなりません。
私が初心者だったころに認識していなかったこととして、定義した全ての変数は共有状態にあるということがあります。
要するにその変数は、その変数と同じスコープにある全ての要素から操作可能であるということです。
スコープがグローバルに近づくほど、この共有状態の治安は悪化します。
可能な限り小さなスコープを保ち、外側に流出しないようにしてください。
複数のリソースが同時にひとつの変数を操作しようとしたときに、グローバルの大きな問題が発生します。
レースコンディションです。
初心者は競合状態、特にデータのロックについての問題への回避策として、タイマーを使用したがる傾向があります。
それは危険信号です。
してはいけません。
コードレビューで指摘し、拒絶してください。
24) エラーを嫌う
エラーはよいことです。
コードが前進していることを意味します。
つまり、改善のフォローアップが簡単に行えます。
プロの開発者はエラーを愛しますが、初心者は嫌います。
エラーが気に障ると考えているのであれば、エラーへの態度を改める必要があります。
エラーはヒントであり、対処し、活用するものであると考えましょう。
いくつかのエラーは例外に改修する必要があります。
例外は、ユーザが定義するエラーの一種です。
また、他のいくつかのエラーはそのままにしておく必要があります。
それらはアプリをクラッシュさせて強制終了させます。
25) 休憩を取らない
あなたはおそらく人間であり、脳には休憩が必要です。
あなたの身体には休息が必要です。
あなたはしばしばゾーンに入り、寝食を忘れて集中することもあるでしょう。
私はそれを初心者の兆候であると考えています。
これは何某かで代替できるようなものではありません。
ワークフローに休息を統合しましょう。
短い休憩をたくさん取り、机から少し離れて周囲を歩き、次に何をするか考えてみましょう。
心機一転してコードに戻ってください。
このポストはとても長いものでした。あなたは休憩が必要です。
読んでくれてありがとう。
2018年3月に"Node.js Beyond The Basics"って本を出したよ。
Node.jsのランタイムに関するいくつかの基本的なトピックといくつかの高度なトピックを解説しているよ。
モジュール、イベント、ストリーム、子プロセス、クラスタなどなど。
期間限定で無償版を提供しているよ。
ここから申し込んでね。
コメント欄
「素晴らしい、いくつかのTIPSを早速私のコードに適用したよ」
「こんな良記事久しぶりに読んだ。全てとは言えないけど、ほとんどについては同意すると言わざるを得ない」
「幾つか引っかかる点もあるけど、全体的には良い記事。長いと感じるなら基礎部分の最初の10個をまず読むといい。」
「"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." ここすき」
「『初心者を罰するために書いたわけではない』『そんな奴は解雇しろ』の対比がナイスだね」
「20年前、ディスプレイに80文字しか表示できなかったころは80文字制限に意味はあったけど、今はこれの2倍あってもいいと思うよ」
「初心者はListを使うなってのはおかしくないか?」「Listは使うべき場所で適切に使うべきで、全てMapを使えというのは悪いアドバイスです」
「『怒っているときは話す前に10数えなさい』私はこれを人生に適用する」
「『再帰のかわりにスタックが使える』例が必要だ例をくれ」
「『ウォーターフォールは死んだ』は真実ではない。それは問題解決手段のひとつに過ぎない。」
「この記事は矛盾でいっぱいだ:(」
「勝手にロシア語訳したら10日で7万人が読んでいったよ」
「26) 既に広く使われているフレームワークを使わずに、独自のフレームワークを作る。不要なヘルパーメソッドを書く。」
感想
25って多くない?選じゃなくない?
とか思っていたら元記事のどこにも選とか書かれてなかった。
この項目はちょっと…、それには例外が…、等と口を挟みたくなるような項目も幾つかないでもないですが、あくまで初心者向けなので基本を大切にしましょうということでしょう。
破離に進むのは守ができるようになってからです。
ただコメントの項目については私は完全に反対で、まずはとにかくコメントを書けと言いたいですね。
コメントがありすぎて困ることより、コメントがなくて困ることの方がずっと多い。
コメントのないコードは、ほとんどの場合コメントのあるコードより読みづらい。
そして不要なコメントは簡単に消せるのに対して、無いコメントを後から付け足すのは難しい。
あとベストプラクティスの項も初心者相手には微妙ですね。
初心者が自力でベストプラクティスを超えたところで性能も可読性も悪化するのが落ちです。
最初のうちはベストプラクティスに従い、次のステップとしてベストプラクティスを超えよう、というのがいいと思います。
ゾーンに入るって英語でもbe in the zone
なんだな。
むしろ英語から来たのかこれ?
gitのところでreversing
ってあるんだけど、これはreverting
の誤字でいいのかな?
git reverse
なんてコマンドないですし。