Awesome Development Rules
は、ソフトウェア開発のベストプラクティスとガイドラインをまとめた、包括的で実践的なリポジトリです。
このリポジトリは、開発者、チーム、組織が、より高品質、保守性、スケーラブルなソフトウェアを開発するための指針を提供することを目的としています。
- コーディングスタイル
- パッケージ原則
- コーディング原則
- レビュー
- テスト
- ドキュメントの整備
- Gitの運用について
- リリース制御(Feature Flag)
- チームビルディング
- リファレンス
- 便利なテンプレート
- お得な情報
コードは明確で読みやすく、コメントを適切に付ける。 ドキュメンテーションコメント(Documentation Comments)は、ソースコード内に挿入される特殊なコメントで、ソフトウェアの要素(関数、クラス、モジュールなど)に対するドキュメンテーションや説明を提供するためのものです。
def count_characters(text: str) -> int:
"""文字列の長さを返します。
この関数は、与えられた文字列の長さを計算し、その値を返します。
Args:
text: 文字列。
Returns:
int: 文字列の長さ。
Raises:
TypeError: 引数が文字列ではない場合。
Examples:
>>> count_characters("Hello world!")
12
Note:
空文字列("")の場合、0を返します。
"""
if not isinstance(text, str):
raise TypeError("引数は文字列である必要があります。")
return len(text)
/**
* これはJSDocコメントです。この関数に対するドキュメンテーションを提供します。
*
* @param {string} parameter パラメータの説明。
* @returns {number} 戻り値の説明。
*/
function exampleFunction(parameter) {
// 関数の実装
return parameter.length;
}
/**
* これはTSDocコメントです。この関数に対するドキュメンテーションを提供します。
*
* @param parameter パラメータの説明。型は`string`です。
* @returns 戻り値の説明。型は`number`です。
*/
function exampleFunction(parameter: string): number {
// 関数の実装
return parameter.length;
}
// * 強調
// ! 警告
// ? 疑問
// TODO: タスク
// FIXME: 不具合がある
// HACK: あまり綺麗じゃない解決策
// XXX: 大きな問題がある
この方法論は、以下の12の要素から成り立っています。
- コードベース
バージョン管理されている1つのコードベースと複数のデプロイ - 依存関係
依存関係を明示的に宣言し分離する - 設定
設定を環境変数に格納する - バックエンドサービス
バックエンドサービスをアタッチされたリソースとして扱う - ビルド、リリース、実行
ビルド、リリース、実行の3つのステージを厳密に分離する - プロセス
アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する - ポートバインディング
ポートバインディングを通してサービスを公開する - 並行性
プロセスモデルによってスケールアウトする - 廃棄容易性
高速な起動とグレースフルシャットダウンで堅牢性を最大化する - 開発/本番一致
開発、ステージング、本番環境をできるだけ一致させた状態を保つ - ログ
ログをイベントストリームとして扱う - 管理プロセス
管理タスクを1回限りのプロセスとして実行する
これらの原則は、パッケージを再利用可能で保守しやすいものにするために役立ちます。 リリースされたものだけを再利用するか、再利用するならばそれをリリースするという考え方です。
つまり、パッケージ内のクラスは、全て再利用可能か、全て再利用不可能かのいずれかにする必要があります。
中途半端にリリースされた状態ではなく、完全にリリースされた状態でなければなりません。
中途半端な状態でリリースされたものを使用すると、自分のコードが予期せぬ書き換えを受ける可能性があります。
一度リリースされたパッケージに起きたコード変更は、全て利用するか、全く利用しないかの二者択一を強調します。
利用者は必要な変更と不要な変更を選択する権限を持たず、パッケージのバージョンアップなどで欲しい機能と都合の悪い機能が一緒に含まれると、ユーザーは新旧バージョンから選択するしかありません。
このような問題は、パッケージに含まれる機能が多すぎることが原因で、適切に分割されていれば必要なパッケージだけを交換でき、影響範囲が小さいため副作用の心配も軽減されます。
アプリケーションの開発においては、共通の機能を持つクラスは同じパッケージにまとめ、パッケージ内のクラスは関連性が高く、凝集度が高いものでなければなりません。 ソフトウェアのパッケージ内に含まれるクラスが同じ種類の変更に対して閉じているべきであることを強調します。
つまり、1つの変更が必要な際、できる限り1つのパッケージだけを交換することを目指す原則です。
パッケージが閉じているとは、そのパッケージ内の責務が自己完結していることを意味します。
よくあるのが、共通的なクラスをたくさん詰め込んだCommonパッケージです。
全てのクラスがCommonパッケージを参照してしまい、影響範囲が予測できなくなります。
関連性の高いクラスは同じパッケージにまとめるべきであり、異なる理由で修正されるクラスは別のパッケージに分けるべきです。
これにより、影響範囲が予測可能であり、保守性が向上します。
パッケージの依存が循環してはならないというシンプルな原則。
循環依存とは、パッケージAを使うにはパッケージBが必要で、パッケージBを使うにはパッケージAが必要と、依存関係がぐるぐる回ってしまう状態のことです。
循環依存が発生すると、デバッグが困難になる(問題が発生した場合、原因となるパッケージを特定するのが困難になる)、リリースが困難になる(あるパッケージを変更すると、循環している他のパッケージにも変更が必要になり、リリースが複雑になる)、テストが困難になる(循環しているパッケージ同士をテストするのが困難になる)といった問題が出てきます。
また、クラス単体ならわかりやすいのですが、パッケージになると、パッケージAに使われるパッケージBがパッケージCを使ったところ、実はパッケージCがパッケージAを使っていたという、気づきにくい循環依存が出来上がってしまいます。
したがって、パッケージ設計をするときは、必ず片方向の依存関係にする。または、強い関係はパッケージ内で閉じるようにする。
パッケージの依存は常により安定したパッケージに向くべきであるという原則。
変更の少ない安定したパッケージに対して依存するようにすることで、変更の影響を最小限に抑えることができます。
ここで大事なのは、安定したパッケージに向けるべきではなく、常に向くことが重要です。
ここでいう安定とは、変更しづらいという意味です。
不安定に依存するとそれ以上の安定はないです。
パッケージの抽象度と安定度は同程度でなければならないという原則。
つまり、安定度の高いパッケージは抽象的でなければならず、逆に安定度の低いパッケージで良い場合は、抽象度が低い(具体的)で良いと言ってます。
SOLID原則は、オブジェクト指向プログラミングのコーディング慣例として広く採用される、5つの基本的な原則を表します。
これらの原則は、ソフトウェアをより理解しやすく、より柔軟に、よりメンテナナンス性の高いものにすることを目的としています。
1つのモジュール(データや関数をまとめた凝集性のあるもの)は1つだけの責務を負うべき。
モジュールが1つの責務だけを持つことで、変更が発生した際に影響範囲を最小限に抑えることができる。
ソフトウェアの構成要素(クラス、関数、モジュール等)は拡張に対して開き、修正に対して閉じていなければならない。
既存のコードを変更せずに新しい機能を追加(拡張)できるようにすることで、柔軟性、再利用性、保守性を確保する。
基本型(親クラス)はその派生型(子クラス)で置換可能であるべき。
派生型が基本型として振る舞えることで、コードの予測可能性と拡張性を確保する。
クライアントは使わないインタフェースの実装を強制されるべきではない。
インタフェースを小さい単位に分離することで、各クライアントは実際に自分が利用するメソッドだけに依存するようになり、他のクライアントの変更の影響を最小限に抑えることができる。
上位のモジュール(上位の機能)は下位のモジュール(下位の機能)に依存してはならない。どちらのモジュール(機能)も抽象に依存すべきである。
抽象は実装の詳細に依存してはならない。実装の詳細が抽象に依存すべきである。
モジュールが具体的な実装に依存していないため、異なるコンテキストでも再利用しやすくなります。
理由:冗長なコードや複雑な構造はバグの原因となりやすく保守性が低下する。
目的:同じコードやロジックを重複せずに再利用すること。
理由:共通の機能やロジックを関数やクラスにまとめることで、変更が発生した場合に一箇所の修正で済み、コードの一貫性が保たれる。
目的:将来の利用を想定せず、不必要な機能や拡張を避けて開発効率を向上させる。
理由:不必要な機能は開発時間の無駄であり、過剰な拡張はコードベースを複雑にし、保守性を低下させるから。
目的:コード内で同じレベルの抽象度を維持すること。
理由:コードの読みやすさを向上させ、理解が容易になり、保守性が向上する。
目的:システムを異なる概念や機能に基づいて複数の独立した部分に分割すること。
理由:関心ごとの分離により、モジュール性が向上し、変更が特定の領域に制限され、保守性と拡張性が向上する。
目的:抽象化が完全でなく、内部の実装の詳細が漏れることを最小限に抑えること。
理由:完全な抽象化が難しい場合、詳細が漏れると、使用者が予測できない挙動や問題に直面する可能性があるため。
コードレビューは、作成者とは異なる視点でコードをチャックすることにより、本人では気付きにくいバグ(ミス、問題点)を発見しやすく、プログラムの品質向上に役立ちます。
また、レビュアー(レビューされる人)とレビュイー(レビューする人)が、お互いコードについて意見交換をすることで、技術知識を共有することができます。
さらに、共通の評価基準(ガイドライン)を作成することで、一貫した品質を保つことができます。
観点 | 概要 | 詳細 |
---|---|---|
Design | アーキテクチャ、設計、設計原則 | アプリケーションとして一貫性のあるアーキテクチャ、設計になっているか。アプリケーションにとって適切な責務、振る舞いになっているか。 |
Functionality | 機能(要件)を満たしているか | 作者の意図通りに動作するか。システムの通信量、パフォーマンスに懸念がないか。 |
Simplicity | 理解容易性 | 可読性のあるコーディングになっているか。処理ができるだけシンプルな振る舞いになっているか。 |
Test | テストの記述、パターンが適切 | システムとして適切な自動テストを兼ね備えているか。自動テストの内容で品質を担保できているか。 |
Naming | クラス、メソッド、変数名などの命名 | 変数やクラス、メソッドに責務を意図した明確な名前が付けられているか。英語文法に誤りがないか。typoもこれに含まれる。 |
Style | コードスタイル | コードスタイルが言語仕様に準拠しているか。 |
Comment | コメント | コメントは明確で有益化か。ドキュメンテーションコメント、アノテーションコメントが適切な内容であるか。 |
Document | ドキュメント | 関連するドキュメントは更新されているか。その内容は適切か。 |
Google's Engineering Practices documentationを参考にしています。
観点 | 詳細 |
---|---|
MUST | 必ず対応してほしいこと。 |
SHOULD | 細かい指摘や気になること。 |
IMO | レビュアー個人の意見。 |
Q | 不明瞭で質問したいこと。 |
レビューガイドラインの7つの観点に加えて、修正要否ガイドラインの観点4つをPrefixに組み合わせ、コメントします。
MUST(Design):◯◯クラスの責務が複数あり、単一責任の原則に反している。
新しい機能を追加した際や、既存の機能を修正した際には、関連するテストを必ず記述する。すべてのテストがパスしてからマージを行う。
開発に関するドキュメントや設計書は適宜更新し、最新の状態を維持する。
長期的な開発とリリースの管理を目的としたブランチ戦略
branch | 説明 |
---|---|
main | 本番環境 |
develop | 開発中のものを置くブランチ |
release | 次にリリースするものを置くブランチ |
feature/* | 新機能開発や不具合の修正に使うブランチ |
hotfix/* | 公開中のもののバグ修正用ブランチ |
シンプルで柔軟なブランチ戦略
branch | 説明 |
---|---|
main | 本番環境 |
feature/* | 作業用ブランチ |
Git-flowとGitHub-flowを組み合わせたブランチ戦略
頻繁なアップデートをmainブランチ(トランク)での直接の開発を促進し、継続的かつ頻繁な統合を重視する戦略
mainに直push禁止原則1ブランチ1機能
作業はfeatureブランチから分岐させる
作業ブランチはマージ後に削除
できるだけレビューする(誰かにしてもらう)
タスクやバグ、機能の追加など、具体的な作業内容はIssueに記述し、それに基づいて作業を行う。
作業が完了したら、Pull Requestを作成し、他のメンバーにレビューを依頼する。
コミットメッセージはPrefixを使い、明確に変更内容を伝えるものとする。
Prefix | 説明 |
---|---|
init | first commit |
chore | ビルドプロセスやドキュメント生成などの補助ライブラリの変更 |
add | 追加(ファイル、機能) |
feat, feature | 追加(機能、ファイル) |
update | 機能修正(バグではない) |
perf | パフォーマンスを向上させるためのコード変更 |
refactor | 整理(リファクタリング等) |
change | 仕様変更 |
fix | バグ修正 |
remove, delete | 削除(ファイル、機能) |
revert | 変更取り消し |
test | 不足しているテストの追加や既存のテストの修正 |
docs | ドキュメンテーションのみの変更 |
merge | ブランチをマージ |
sleep | 寝たw |
Prefix | 説明 |
---|---|
add | 追加(ファイル、機能) |
remove, delete | 削除(ファイル、機能) |
update | 機能修正(バグではない) |
fix | バグ修正 |
- init
- chore: Reactと依存関係を追加
- add: ユーザー登録機能を追加
- sleep: I'm so sleepy
※Feature Flagとは別にFuture Flagというものもある
- 開発中の機能を一部のユーザーに先行公開し、テストやフィードバックを得ながら段階的にリリース
- ユーザーごとに機能のオン・オフを切り替えることで、A/Bテストや多人数テストを実施
- 問題が発生した場合、コードの変更を待たずに機能を迅速に無効化
- ロールバックによる影響範囲を最小限に抑え、ユーザーへの影響を軽減
- 本番環境にデプロイ後、ユーザーへの影響を確認してから機能を有効化
- 不具合が発生した場合でも、コードの変更なしで迅速にロールバック
- 新しいデザインのUIを特定のユーザーに先行公開
- 有料プランの機能を無料プランユーザーに一時的に開放
- 地域やデバイスごとに機能の表示を制御
- バグ修正中の機能を一時的に無効化
export const Page = () => {
if (featureFlag) {
return <NewFeature />
} else {
return <CurrentFeature />
}
}
会議の参加者が多いほど、生産性は低下すると考えられています。これを解決するために、参加メンバーを2枚のピザを分け合える人数に抑えるのです。大勢が会議に参加すれば、創造性が犠牲になるという考え方が背景にあります。
しかし、このルールを単純に利用するだけでは意味がないと指摘されています。大企業の場合、チーム1つ1つは小規模であっても、自律性や生産性が失われる可能性があります。そのため、このルールを適用する際には、チームの自律性やミッションを考慮することが重要とされています。
また、会議の進行役を任命したり、基本的なルールを設けたり、議題が参加者全員と関係あるかを事前に確認するなどの工夫が必要とされています。このように、「二枚のピザルール」は、会議の効率性と生産性を向上させるための一つの戦略と言えます。
Googleの人工知能チームが採用している「15分ルール」は、問題解決のための効率的なアプローチを提供します。- 最初の15分は自分で解決を試みます。
- 15分経っても解決できない場合は、必ず他の人に聞きます。
このルールの目的は、時間の最適な利用と効率的な問題解決を促進することです。 前者のステップを守らないと他人の時間を無駄にし、後者のステップを守らないと自分の時間を無駄にするとされています。
このルールはあくまで一つの指標であり、個々の状況により適切な時間やルールを決めることが重要です。
質問をする際は、自分が試したことや調べた内容を明示することで、質問を受ける側が解決策を見つけやすくなります。- 最も適切なチャネルで質問するようにしてください。同じメッセージを複数のチャネルに投稿しないでください。
- 質問してもよいかどうかを尋ねるのではなく、ただ質問してください。できるだけ詳細を含めてください。
- 質問する前によく調べてください。ドキュメントを検索し、検索エンジンを使用し、時間をかけて自分で答えを見つけるのは素晴らしい学習方法です。
- 質問の回答に時間がかかる場合は、スレッドを作成してください。これは、他の人が混乱したり、チャネル内を何度もスクロールしたりすることなく、同時に質問できることを意味します。同様に、質問に質問で答える場合は、スレッドを作成してください。
- あなたを助けるために費やした人々の時間を尊重してください。
- たとえ以前にあなたを助けてくれたことがあるとしても、助けを求める人に DM を送らないでください。サーバー内でのみ質問することで、誰もが利益を得ることができます。
コードを共有する必要がある場合がよくあります。これを行うには主に2つの方法があります。
- コードの記述量が少ない場合は、マークダウン構文を使用する。
- コードの記述量が多い場合は、GitHubのリポジトリやGistのリンクを共有する。
コードの画像を投稿すると、読みにくいし、文字のコピーなどができないため、ヘルプを提供するのが難しくなる。 スクリーンショットは、ブラウザでの出力の問題を示す目的でのみ使用してください。
PREP法とは、Point(ポイント)、Reason(理由)、Example(例)、Point(ポイント)の頭文字を取ったもので、意見やアイデアを効果的に伝えるためのコミュニケーション手法です。具体的な手順は以下の通りです。
- Point(ポイント):最初に主張や結論を述べます。これはあなたが何を伝えたいのかを明確にするためのものです。
- Reason(理由):次に、その主張や結論を支持する理由を述べます。これはあなたの主張が信頼できるものであることを示すためのものです。
- Example(例):その後、具体的な例やエビデンスを提供します。これはあなたの理由が具体的で実際的なものであることを示すためのものです。
- Point(再度ポイント):最後に、最初の主張や結論を再度述べます。これはあなたのメッセージを強調し、リスナーにそれを覚えてもらうためのものです。