- 原則とは考える側面である
- 単純に従うことではなく、その原則が提供する「考える側面」について考えた上で行動することが大切
- SOLID
- S(Single Responsibility): 単一責任
- O(Open-Closed): 拡張にはオープン、変更にはクローズド
- YAGNI(You ain't gotta need it)
- L(Liskov Substitution): リスコフの置換原則
- ある型のオブジェクトが規定する動作を、その派生型のオブジェクトのものに置き換えても、 その動作はオブジェクトの利用(開発者など)にとって予想可能である
- I(Interface Segregation): インターフェース分離
- 使わないインターフェースを実装したり使われないインターフェースに依存したりしないようにインターフェースを分ける
- D(Dependency Inversion): 依存性の逆転
- 別の型に依存するとき、具体的な型ではなく抽象的な型に依存するようにする
- DI(Dependency Injection、依存性の注入)を行う
- KISS(Keep it simple/short/straightforward/silly)
- 認知負荷を下げる
- 原則に従って認知負荷が上がる場合、従わない方がいいかも
- DRY(Don't repeat yourself)
- 繰り返しは共通化する
- やり過ぎない(認知負荷が上がる)
- スコープを減らす
- ブロック→関数→ファイル→モジュール・パッケージ→グローバル
- 値の影響範囲を減らし、認知負荷を下げるため
- 変数よりも定数
- 意図しない書き換えを防ぎ、認知負荷を下げるため
- 実行時よりもコンパイル時
- 計算時間を減らすため
- 変数は初期化する
- 不正な値を防ぐため
- 読み込まれることのない書き込みを消すのは最適化の仕事とする
- 型推論を使う
- 長すぎる型名を省略するため
- 型の変更をダックタイピング的に許容するため
- 認知負荷が上がりすぎる場合には明示的に型を指定する
- コピーよりもムーブ
- メモリアクセスを減らすため
- 認知負荷が上がりやすいので注意
- 番兵(本来とは異なる特殊な意味を持つ値。
0とか-1とか最大値とか)を使わない- バグになるのを防ぐため
- 特殊な意味は別に変数を持って表す
- リフレクションは使わない
- 認知負荷が上がるの防ぐため
- テストコードは例外
- 公開されていない対象へのアクセスに必要かも
- Fail fast(なるべく早く失敗させる)
- 不正な値について正常系の処理をするとどうなるか、という検討を不要にするため
- なるべくエラーや例外的な状況でネストが深くなるようにする
- 正常系のコードのネストを減らし認知負荷を下げるため
- ユーザの入力はバリデーションする
- サニタイズしない
- 脆弱性を防ぐため
- 高速化の順番
- プロファイリングをする
- 遅い箇所を推測しない
- アルゴリズムの変更
- 並列化
- プロファイリングをする
- 並列化で気をつけること
- データハザード(書き込みの前後の読み書きの順番)
- デッドロック
- ロック・同期の代わりにアトミックな変数・メモリバリア(処理の順序を強制し、前の処理が終わったことを別のスレッドから見えるようにする)が使えるか検討する
- ロック・同期は遅い
- コードを書き始めるとき
- 予め
- formatter/linter/静的解析を使う
- CI/CDを準備する
- 後からだとリファクタが大変
- 予め
- 関連ツール
- formatter:
clang-format - linter:
cpplint - 動的解析: コンパイラのフラグ、valgrind
- formatter:
- RAII(Resource Acquisition Is Initialization)
- メンバ変数はコンストラクタで初期化し、デストラクタで廃棄する
- 失敗時に既に確保したリソースがあれば廃棄する
- リソースリークを防ぐため
- Cではリソース解放用のコードを用意してラベルを貼り
gotoする - C++では例外をスローしてスタックの巻き戻しにより対応する(メンバ変数のデストラクタに任せる)
- スマートポインタを使う
- リソース(メモリ、ハンドル、接続など)のリークを防ぐため
- 単一所有者:
std::unique_ptr - 複数所有者:
std::shared_ptr- 循環参照がある場合は
std::weak_ptrを検討する
- 循環参照がある場合は
- Interger promotionを意識する
friendを使わない- カプセル化を守るため
- 関連ツール
- formatter: prettier
- linter: ESLint
- ルールが膨大なため、ルールセットと併せて導入する
- ビルトインのオブジェクトを使う場合は必ず実行環境で使えるか確認する
- 古いブラウザやNode.jsでは使えないことがある
- あるオブジェクトのプロパティに対応する値にアクセスして
undefinedが得られた場合、状態が2パターンあることに注意- プロパティに対応する値として
undefinedが入っている場合 - プロパティそのものが存在しない場合
- プロパティに対応する値として
Array.prototype.sort()は、値を文字列に変換して辞書順ソートとなることに注意
- 型推論:
var - 並列ソートが標準で存在する(
parallelSort)
- 配列は順序付きのマップであることに注意する
- プリペアードステートメントにする
- 典型的なSQLインジェクションを防ぐため
- ストアドプロシージャを使わない
- 引数の型がプロシージャの定義と与えられた値で異なる場合、エラーとならず不正な値となる
- 表を変数に格納できない
- クライアント側でプロシージャライクにまとめる
- トランザクションをネストしない
- トランザクション中に新たにトランザクションを開始しない
- 既存のトランザクションがコミットされてしまう
SAVEPOINTで代替できるか検討する
- トランザクション分離レベル
SERIALIZABLE以外は、あるトランザクションで実行した変更は、例えCOMMIT後でも別のトランザクションからではすぐに読まれないことに注意する(強一貫性ではない)
- MySQL互換データベースで使えない機能を把握しておく
- Percona XtraDB Clusterではトランザクション分離レベル
SERIALIZABLEは使用不可
- Percona XtraDB Clusterではトランザクション分離レベル
- Consistency Level
- 基本的に書き込みは
EACH_QUORUM、読み込みはLOCAL_QUORUMにする- 強一貫性(書き込みが即座にノード・クラスタ間で反映される)
- 要件によって弱いレベルを検討する
ALLは使わない- 1ノードでもダウンしたら必ず失敗になる
- 基本的に書き込みは
- 読み書きの失敗の再試行はRetry Policyではなくクライアント側で行う
- Retry Policyは意味論が不明瞭で、バグがクライアント側かドライバ側か分かりづらい
- あるキーの範囲でアクセスする時、クライアントでキーを列挙してアクセスする
- 範囲に当てはまるかどうかを判別するために全検索されるのを防ぐ
BATCH句は必要最小限にする