前書き
プログラミングの原理原則は、コードを効率的で理解しやすいものにするための基本的なガイドラインです。
基礎となる原則
1. 明確で簡潔なコードを書く
読みやすさ
- 他の人や自分が後で見たときに理解しやすいコードを書く
コメント
- 必要に応じてコメントを追加し、コードの目的や動作を説明する
2. 一貫性を保つ
命名規則
- 変数や関数の名前を一貫したルールで付ける
- 例)
snake_case
やcamelCase
- 例)
コードスタイル
- インデントやスペースの使い方を統一する
3. テストとデバッグ
小さな単位でテストする
- コードを書いたら、すぐに動作を確認する
- 小さな単位でテストすることで、バグを早期に発見する
デバッグ
- 「エラーメッセージ」や「デバッグツール」を使って問題を特定し、修正する
4. 再利用性を高める
関数やメソッド
- 繰り返し使う処理は、関数やメソッドにまとめる
モジュール化
- コードを小さな部品に分けて、それぞれが独立して動作するようにする
5. データの適切な扱い
データ型の理解
- 変数に適切なデータ型を使い、間違った型を使わないようにする
バリデーション
- 入力データが正しいかどうかを確認する
6. セキュリティ
入力の検証
- ユーザーからの入力は必ず検証し、安全性を確認する
機密情報の保護
- パスワードやAPIキーなどの機密情報を適切に管理する
7. 効率性
アルゴリズムの選択
- 適切なアルゴリズムやデータ構造を選ぶことで、プログラムの効率を高める
リソース管理
- メモリやCPUの使用量を考慮し、無駄なリソース消費を避ける
8. 学び続ける
継続的な学習
- 新しい技術やツールを学び、スキルを向上させる
レビューとフィードバック
- 他の人のコードをレビューしたり、自分のコードをレビューしてもらったりすることで、改善点を見つける
有名な原理原則
1. DRY(Don't Repeat Yourself)
意味
- 同じコードやロジックを繰り返し書かない
理由
- 重複したコードはバグの温床になり、修正が必要なときに複数箇所を変更しなければならない
- そのため、メンテナンスが難しくなる
実践方法
- 繰り返し使う処理を関数やメソッドにまとめる
- ただし、「完全に同一であり、普遍であるメソッド」に限る
- 将来差分が発生する可能性があるメソッドを共通関数にすると、「 KISS原則 」と相反する場合がある
# DRY原則に反する例
user1_greeting = "Hello, Alice!"
user2_greeting = "Hello, Bob!"
# DRY原則を守る例
def greet_user(name):
return f"Hello, {name}!"
user1_greeting = greet_user("Alice")
user2_greeting = greet_user("Bob")
2. KISS(Keep It Simple, Stupid)
意味
- コードはシンプルで理解しやすく保つべき
理由
- 複雑なコードはバグを生みやすく、理解しにくいため、メンテナンスが難しくなる
実践方法
- 必要以上に複雑なロジックを避け、シンプルな解決策を選ぶ
- DRY原則と相反することがあるので、相反した時の兼ね合いが大事になる
- シンプルにするには、将来差分が発生する可能性があるメソッドにおいては、共通メソッドにしない方が良い場合がある
# KISS原則に反する例
def calculate_area(shape, dimensions):
if shape == 'rectangle':
return dimensions[0] * dimensions[1]
elif shape == 'circle':
import math
return math.pi * (dimensions[0] ** 2)
else:
return None
# KISS原則を守る例
def calculate_rectangle_area(width, height):
return width * height
def calculate_circle_area(radius):
import math
return math.pi * (radius ** 2)
3. SOLID
SOLID原則は、オブジェクト指向プログラミングにおける設計の基本原則を集めたものです。
これらの原則を守ることで、コードの保守性や拡張性が向上します。
3-1. 単一責任の原則(Single Responsibility Principle, SRP)
意味
- クラスやモジュールは、たった一つの責任を持つべき
理由
- 一つの責任に集中することで、変更の影響範囲を小さくし、コードの理解や保守が容易になる
# SRPに反する例
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self):
# データベースにユーザーを保存する処理
# SRPを守る例
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user):
# データベースにユーザーを保存する処理
3-2. オープン・クローズドの原則(Open/Closed Principle, OCP)
意味
- ソフトウェアのエンティティ(クラス、モジュール、関数など)は、拡張に対して開かれており、修正に対して閉じているべき
理由
- 新しい機能を追加する際に既存のコードを変更せずに済むため、バグのリスクを減らし、保守性を高める
# OCPに反する例
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
def print_area(shape):
if isinstance(shape, Rectangle):
print(shape.area())
elif isinstance(shape, Circle):
print(shape.area())
# OCPを守る例
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
def print_area(shape: Shape):
print(shape.area())
3-3. リスコフの置換原則(Liskov Substitution Principle, LSP)
意味
- 派生クラスは、基底クラスと置き換えてもプログラムの正しさが保たれるべき
理由
- 派生クラスが基底クラスの契約を守ることで、一貫性が保たれ、コードの予測可能性が向上する
# LSPに反する例
class Bird:
def fly(self):
pass
class Ostrich(Bird):
def fly(self):
raise Exception("Ostriches can't fly")
# LSPを守る例
class Bird:
def move(self):
pass
class FlyingBird(Bird):
def fly(self):
pass
class Ostrich(Bird):
def move(self):
print("Running")
class Sparrow(FlyingBird):
def fly(self):
print("Flying")
3-4. インターフェース分離の原則(Interface Segregation Principle, ISP)
意味
- クライアントは、使わないメソッドへの依存を強制されるべきではない
理由
- 大きなインターフェースを小さなインターフェースに分割することで、クライアントが必要な機能だけを使用できるようにし、変更の影響を小さくする
# ISPに反する例
class Worker:
def work(self):
pass
def eat(self):
pass
class Robot(Worker):
def work(self):
print("Working")
def eat(self):
raise Exception("Robots don't eat")
# ISPを守る例
class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
print("Working")
def eat(self):
print("Eating")
class Robot(Workable):
def work(self):
print("Working")
3-5. 依存性逆転の原則(Dependency Inversion Principle, DIP)
意味
- 高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両者は抽象に依存すべき
理由
- 具体的な実装に依存しないことで、柔軟性が増し、変更に強くなる
# DIPに反する例
class LightBulb:
def turn_on(self):
print("LightBulb: ON")
def turn_off(self):
print("LightBulb: OFF")
class Switch:
def __init__(self, bulb: LightBulb):
self.bulb = bulb
def operate(self):
self.bulb.turn_on()
# DIPを守る例
class Switchable:
def turn_on(self):
pass
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
print("LightBulb: ON")
def turn_off(self):
print("LightBulb: OFF")
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
self.device.turn_on()
4. YAGNI
YAGNI(ヤグニ)とは、「You Aren't Gonna Need It」の略で、ソフトウェア開発における重要な原則の一つです。
この原則は、将来必要になるかもしれないと考えて機能やコードを書き加えるのではなく、今必要なものだけを実装するべきだという考え方です。
基本的な考え方
1. 現在必要なものに集中する
- 将来の必要性を予測してコードを追加するのではなく、現時点で必要な機能やコードだけを実装する
2. 無駄な作業を避ける:
- 実際に必要となるかどうか分からない機能を追加することで、時間とリソースが無駄になる可能性がある
3. コードの複雑さを減らす
- 不必要な機能やコードを追加することで、コードベースが複雑になり、メンテナンスが難しくなることを避ける
メリット
シンプルなコード
- 必要な機能だけを実装することで、コードがシンプルで分かりやすくなる
保守性の向上
- 不要な機能が少ないため、バグの発生率が低くなり、コードの保守が容易になる
開発効率の向上
- 今必要な作業に集中することで、開発の効率が向上する
実践例
BAD
# 将来、ユーザーの役割に基づいて異なる権限を管理するかもしれないと考えて、複雑な権限管理システムを実装
class User:
def __init__(self, name, role):
self.name = name
self.role = role
self.permissions = self.set_permissions()
def set_permissions(self):
if self.role == 'admin':
return ['read', 'write', 'delete']
elif self.role == 'user':
return ['read', 'write']
else:
return ['read']
# しかし、現在の要件では、すべてのユーザーが同じ権限を持っている
GOOD
# 現在の要件に基づいて、シンプルなユーザークラスを実装
class User:
def __init__(self, name):
self.name = name
self.permissions = ['read', 'write']
# 将来的に必要になったときに、権限管理システムを追加する
5. 車輪の再発明
「車輪の再発明」という表現は、既に存在し、広く使用されている技術や方法を無駄に再開発することを意味します。
主なポイント
1. 無駄な労力
- 既に存在する解決策を知らずに、同じ問題に対して新たに解決策を作り出すことは、時間とリソースの無駄につながる
2. 既存の解決策の再利用
- 既存のライブラリ、フレームワーク、ツールを利用することで、効率的に開発を進めることができる
- これにより、新しい機能の開発や改善に集中できる
3. 品質と信頼性
- 既存の解決策は、多くのユーザーや開発者によってテストされ、改善されてきたものが多く、品質や信頼性が高いことが多い
- 自前で新たに開発した場合、同じレベルの品質や信頼性を確保するには、多くの時間と努力が必要である
4. 学びと成長
- 既存の解決策を理解し、学ぶことで、自身のスキルや知識を向上させることができる
- 他の開発者のアプローチや考え方を学ぶこともできる
6. プログラマの3大美徳
プログラマの三大美徳とは、ラリー・ウォール(Larry Wall)によって提唱された、優れたプログラマが持つべきとされる三つの特性のことです。
1. 怠惰(Laziness)
意味
- プログラマは、労力を最小限に抑えるために効率的な方法を探し、繰り返しの作業を自動化しようとする
利点
- 再利用可能なコードを書くことで、同じ作業を繰り返さない
- ドキュメントやツールを整備して、他の人が同じ作業を簡単に行えるようにする
2. 短気(Impatience)
意味
- プログラマは、遅いシステムや非効率なプロセスに対して我慢できず、それを改善しようとする
利点
- レスポンスの速いシステムを求めることで、ユーザーエクスペリエンスが向上する
- 効率的なコードを書くことで、全体のパフォーマンスが向上する
3. 傲慢(Hubris)
意味
- プログラマは、自分のコードが他人に批判されないように、高品質なコードを書くことを誇りに思う
利点
- 品質の高いコードを書くことで、バグが少なく、メンテナンスしやすいシステムが構築される
- 自分の成果に責任を持ち、他人に迷惑をかけないようにする
まとめ
- 怠惰 : 効率化と自動化を追求する
- 短気 : パフォーマンスとユーザーエクスペリエンスを重視する
- 傲慢 : 高品質なコードを書くことを誇りに思う
7. ラバーダッキング
ラバーダッキング(Rubber Duck Debugging)とは、プログラミングにおけるデバッグの手法の一つです。
問題を解決するためにコードの動作を詳細に説明することで、問題点を明確にする方法です。
この手法の名前は、プログラマがラバーダック(ゴム製のアヒルの人形)に向かってコードを説明するというエピソードに由来します。
手順
1. 問題の説明
- 自分が直面している問題を、ラバーダック(または他の無生物)に向かって説明する
2. コードの解説
- 問題のあるコードを一行ずつ、詳細に説明する
- なぜそのコードを書いたのか、どのように動作するはずなのか
3. 気づき
- コードを説明する過程で、自分の思考が整理され、見落としていたバグやロジックの誤りに気づくことが多い
利点
思考の整理
- 自分の考えを言葉にすることで、問題の本質や見落としていた点に気づきやすくなる
独立性
- 他人の助けを借りずに、自分自身で問題を解決できるようになる
コミュニケーションの向上
- 自分のコードを他人に説明するスキルが向上し、チーム内でのコミュニケーションが円滑になる
まとめ
- 言語化して説明するという単純な行為が、効果的なデバッグになる
- クラスヘッダー、関数ヘッダーを書くと言う行為も同様に、効果的なデバッグとなる
8. 割れ窓の法則
割れ窓の法則(Broken Windows Theory)は、元々犯罪学の分野で提唱された理論ですが、ソフトウェア開発や組織運営にも応用されています。
この理論は、小さな問題や欠陥を放置すると、やがて大きな問題や劣化を引き起こすという考え方を示しています。
基本概念
小さな問題を放置しない
- 割れた窓を放置すると、周囲の環境全体が劣化し、さらなる犯罪や破壊行為を招くとされる
早期の対応が重要
- 小さな問題や不具合を早期に修正することで、全体の品質や秩序を保つことができる
応用
コードの品質管理
- 小さなバグやコードの不備を放置すると、コードベース全体の品質が低下し、最終的には大規模な問題を引き起こす可能性があ
継続的なリファクタリング
- 定期的にコードを見直し、改善することで、技術的負債を防ぎ、システムの健全性を保つ
チーム文化の維持
- 小さな規律違反や不正行為を見逃さないことで、健全なチーム文化を維持し、全体の生産性を向上させる
具体例
- コードレビューで見つかった小さなスタイル違反や軽微なバグをそのままにしておくと、次第に同様の問題が増え、コード全体が読みにくく、保守しにくくなる
- これが放置されると、大きなバグやシステム障害を引き起こす可能性が高まる
9. 名前重要
名前重要原則とは、変数、関数、クラスなどの名前を適切に付けることが、コードの可読性や保守性に大きな影響を与えるという考え方です。
適切な名前を付けることで、コードの意図や機能が明確になり、他の開発者や将来の自分がコードを理解しやすくなります。
命名は最重要課題
名前はコードを読む人へのUIです。
適切に命名されているコードは、「何を」、「どのように」行なっているかを理解できます。
メソッド名を見ただけで概要が把握できたり、変数の持つデータの内容を理解できたり、開発者体験を上げてくれる基礎となります。
主なポイント
1. 意味のある名前を付ける
- 名前は、その変数や関数、クラスが何を表しているのかを正確に示すべき
-
calculateTotal
、userAge
、ProductRepository
など
-
2. 一貫性を保つ
- プロジェクト全体で命名規則を統一し、一貫性を保つことで、コードの読みやすさを向上させる
- キャメルケース(
userAge
)、スネークケース(user_age
)など
- キャメルケース(
3. 短すぎず、長すぎず
- 名前は短すぎて意味が伝わらないものや、長すぎて読みにくいものは避ける
-
a
やx1
などの短すぎる名前 -
calculateTheTotalOfAllItemsIncludingTaxesAndDiscounts
などの長すぎる名前
-
4. コンテキストを考慮する
- 名前は、その名前が使われるコンテキストに依存して適切に付けるべき
- 「クラス内のプライベート変数にはクラス名を含める必要はないが、グローバル変数には含める」など
5. 標準的な命名法を使用する
- 業界標準や言語固有の命名規則に従うことで、他の開発者がコードを理解しやすくなる
- JavaScriptではキャメルケース、Pythonではスネークケースが一般的である
10. ボーイスカウトの規則(Boy Scout Rule)
プログラミングにおけるボーイスカウトの規則(Boy Scout Rule)は、コードを修正する際にそのコードを「見つけたときよりもきれいにしておく」という原則です。
この考え方は、ソフトウェア開発者がコードベースの品質を継続的に改善するための指針として広く受け入れられています。
主なポイント
1. 小さな改善を積み重ねる
- 大規模なリファクタリングをするのではなく、日々の作業の中で少しずつコードを改善していくことが重要である
- 不要なコメントを削除する
- 変数名をより意味のあるものに変更する
- 単純なバグを修正する
2. コードの品質を維持・向上する
- コードを修正する際、既存のコードの品質を低下させないようにし、可能な限り改善を加えることで、時間とともにコードベース全体の品質が向上する
- 既存の関数を改善して読みやすくする
- 重複コードをリファクタリングして共通の関数にまとめる
3. 持続可能な開発
- 継続的に小さな改善を行うことで、技術的負債を減らし、長期的なプロジェクトの健全性を保つことができる
- 毎回のコミットで少しずつ改善を加えることで、大規模なリファクタリングが不要になる
まとめ
これらの原則を意識してプログラミングを行うことで、効率的で理解しやすいコードを書くことができます。
プログラミングは継続的な学習が重要なので、これらの原則を実践しながら成長していくことが大切です。