Upgrade to Pro — share decks privately, control downloads, hide ads and more …

リーダブルコード要約

Avatar for morishita-db morishita-db
May 30, 2025
77

 リーダブルコード要約

Avatar for morishita-db

morishita-db

May 30, 2025
Tweet

Transcript

  1. 目次 第1 章: 理解しやすいコード 第2 章: 名前に情報を詰め込む 第3 章: 誤解されない名前

    第4 章: 美しさ 第5 章: コメントすべきことを知る 第6 章: コメントは正確で簡潔に 第10 章: 汎用コードの分離 第11 章: 一度に1 つのことを 第13 章: 短いコード を書く
  2. 第1 章: 過度なリファクタリングの落とし穴 悪い例 ❌ - 短すぎて理解に時間がかかる 良い例 ✅ -

    適切な長さで理解しやすい ポイント: 短いコードが必ずしも良いコードではない。理解しやすさを最優先に def process(u) u.orders.select(&:paid?).map(&:total).sum.tap { |t| NotificationService.new.notify(u, t) if t > 10000 } end def calculate_paid_orders_total_and_notify(user) paid_orders = user.orders.select(&:paid?) total_amount = paid_orders.sum(:total) if total_amount > 10000 NotificationService.new.notify(user, total_amount) end total_amount end
  3. 第2 章: 名前に情報を詰め込む 要約 テーマ: 名前に情報を詰め込む 明確な単語を選ぶ tmp などの汎用的な名前を避ける 抽象的な名前ではなく、具体的な名前を使って物事を詳細に説明する

    変数名に大切な情報を追加する スコープの大きな変数には長い名前をつける 省略していい単語については省略してもいい (string-> str, document->doc)
  4. 1. 明確な単語を選ぶ # 悪い例 ❌ - 曖昧な単語 def get_data(user_id) #

    何のデータ? def send_notification(user) # どこに送る? def process_order(order) # 何をprocess ? def handle_payment(amount) # どう処理する? # 良い例 ✅ - 明確な単語 def fetch_user_profile(user_id) # DB からプロフィール取得 def deliver_email_notification(user) # メール送信 def charge_order_payment(order) # 決済実行 def validate_credit_card(card_info) # クレカ検証
  5. カラフルな単語の代替案: send → deliver, dispatch, transmit, notify, submit find →

    fetch, retrieve, locate, search, lookup start → initialize, launch, begin, trigger, activate make → create, generate, build, produce, construct 質問: 他何か思いつく単語はありますか?
  6. 第2 章: 汎用的な名前を避ける ポイント: tmp, data, info 等の汎用的な名前は具体的な名前に置き換える # 悪い例

    ❌ tmp = user.orders.sum(:total) data = User.where(active: true) info = { status: 'success', count: 10 } # 良い例 ✅ total_revenue = user.orders.sum(:total) active_users = User.where(active: true) api_response = { status: 'success', count: 10 }
  7. 第2 章: 抽象的ではなく具体的な名前 ポイント: 何をするかが一目で分かる具体的な名前を使う # 悪い例 ❌ class Manager

    def process(item) # 何をprocess するのか不明 end end # 良い例 ✅ class OrderManager def process_payment(order) # 注文の支払い処理と明確 end end
  8. 第2 章: 変数名に重要な情報を追加 ポイント: セキュリティや状態に関する重要な情報を名前に含める # 悪い例 ❌ password =

    params[:password] html = render_template(data) users = User.all # 良い例 ✅ plain_text_password = params[:password] # 平文であることを明示 unsafe_html = render_template(data) # サニタイズ前であることを明示 unfiltered_users = User.all # フィルタリング前であることを明示
  9. 第2 章: スコープに応じた名前の長さ ポイント: スコープが広いほど、より説明的な長い名前を使う # 短いスコープ → 短い名前でOK orders.each

    do |o| puts o.total end # 長いスコープ → 詳細な名前 class UserAccountManager def initialize @pending_verification_users = [] @email_notification_queue = [] end end
  10. 第2 章: 適切な省略と意味のある記号 ポイント: 一般的な省略は可、混乱を招く省略は避ける # 良い省略例 ✅ str =

    "Hello World" # string → str doc = Document.find(id) # document → doc img = user.profile_image # image → img # 適切な省略例 ✅ calc_total_amount = calculate_total_amount # calc_ プレフィックスは明確 calc_discount_rate = calculate_discount_rate # 何を計算するかが分かる calc_tax = calculate_tax_amount # 適切な省略 # アンダースコアで区別 def admin? # 管理者判定 end def _internal_method # プライベートメソッドの慣例 end unsafe_user_input = params[:content] # unsafe_ プレフィックス
  11. 第2 章: 単位や重要な属性を含める ポイント: 単位、型、重要な属性を名前に含めて誤解を防ぐ # 悪い例 ❌ delay =

    5 timeout = 30 input = params[:content] # 良い例 ✅ delay_seconds = 5 timeout_ms = 30_000 max_retry_count = 3 unsafe_user_input = params[:content]
  12. 第3 章: 誤解されない名前 要約 名前が複数の意味に解釈される可能性を排除する ex) filter ( 選択するのか除外するのかがわからない) 限界値を含めるときはmin,

    max を使用する 限界値を明確にするには、名前の前にmax やmin をつけよう 範囲を指定するときはfirst, last を使う 単語に対するユーザーの期待にも注意する ex) get() やsize() などにはメンバや、要素数を返却することを期待している
  13. 第3 章: 複数の意味に解釈される名前を避ける ポイント: 動作が曖昧な単語は避け、具体的な動作を表現する # 悪い例 ❌ - filter

    は曖昧 def filter_users(users, criteria) # 条件に合うユーザーを「選択」するのか # 条件に合うユーザーを「除外」するのか不明 end # 良い例 ✅ - 意図を明確に def select_active_users(users) users.where(active: true) end def exclude_banned_users(users) users.where.not(status: 'banned') end
  14. 第3 章: 限界値にはmin/max を使用 ポイント: 「以上」 「以下」 「未満」 「超過」を明確に区別 #

    悪い例 ❌ - 境界が不明確 class Product < ApplicationRecord validates :price, numericality: { greater_than: 0, less_than: 1000000 } end # 良い例 ✅ - 境界を明確に class Product < ApplicationRecord MIN_PRICE = 1 MAX_PRICE = 999999 validates :price, numericality: { greater_than_or_equal_to: MIN_PRICE, less_than_or_equal_to: MAX_PRICE } end
  15. 第3 章: 範囲にはfirst/last を使用 # 悪い例 ❌ - 範囲が曖昧 class

    EventsController < ApplicationController def index start_date = params[:begin] # 開始日を含む? end_date = params[:finish] # 終了日を含む? @events = Event.where(date: start_date..end_date) end end # 良い例 ✅ - 範囲を明確に class EventsController < ApplicationController def index first_date = params[:first_date] # この日を含む last_date = params[:last_date] # この日も含む @events = Event.where(date: first_date..last_date) end end
  16. 第3 章: ユーザーの期待に応える 悪い例 ❌ - 期待と異なる動作 class User <

    ApplicationRecord def get_orders # get なのにDB に副作用がある self.last_accessed_at = Time.current save! orders end def size # size なのに重い処理 orders.joins(:line_items).sum(:quantity) end end
  17. 良い例 ✅ - 期待通りの動作 ポイント: get/size などの慣習的な意味を守り、副作用は明示する class User <

    ApplicationRecord def orders # 単純にアソシエーションを返す super end def total_order_items_count # 重い処理であることを名前で表現 orders.joins(:line_items).sum(:quantity) end def update_last_accessed! # 副作用があることを明示 update!(last_accessed_at: Time.current) end end
  18. 第4 章: 美しさ 要約 コードの単純な美しさの改善は取り組みやすいのに、すごく読みやすくなる 大きなリファクタリング ( 新しい関数やクラスの導入など) がもっとうまくいくようになる事が多い 美しさと優れた設計の両方を追求するべき

    主に、コードを読みやすくするための 余白・配置・順序 について 以下の3 原則 読み手が慣れているパターンと一貫性のあるレイアウトを使う 似ているコードは似ているように見せる 関連するコードをまとめてブロックにする
  19. 第4 章: 美しさ 悪い例 ❌ - レイアウトが乱雑 class User <

    ApplicationRecord validates :email, presence: true has_many :orders validates :name, presence: true, length: { maximum: 50 } belongs_to :company, optional: true validates :age, numericality: { greater_than: 0 } has_many :reviews, dependent: :destroy end
  20. 第4 章: 美しさ 良い例 ✅ - 関連するものをグループ化 ポイント: 関連する要素をグループ化し、一貫した間隔を保つ class

    User < ApplicationRecord # アソシエーション belongs_to :company, optional: true has_many :orders, dependent: :destroy has_many :reviews, dependent: :destroy # バリデーション validates :email, presence: true, uniqueness: true validates :name, presence: true, length: { maximum: 50 } validates :age, numericality: { greater_than: 0 } end
  21. 第4 章: コントローラーでの美しさ class UsersController < ApplicationController before_action :authenticate_user! before_action

    :set_user, only: [:show, :edit, :update, :destroy] def index @users = User.includes(:company) .page(params[:page]) .per(20) end private def set_user @user = User.find(params[:id]) end def user_params params.require(:user) .permit(:name, :email, :age, :company_id) end end
  22. 第4 章: Gemfile での美しさ 悪い例 ❌ - 無秩序な配置 gem 'rails',

    '~> 7.0.0' gem 'rspec-rails' gem 'sqlite3' gem 'puma' gem 'factory_bot_rails' gem 'bootsnap', require: false gem 'redis' gem 'pry-rails'
  23. 第4 章: Gemfile での美しさ 良い例 ✅ - 目的別にグループ化 # Core

    gem 'rails', '~> 7.0.0' gem 'bootsnap', require: false # Database & Cache gem 'pg', '~> 1.1' gem 'redis', '~> 4.0' # Server gem 'puma', '~> 5.0' group :development, :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'pry-rails' end group :development do gem 'web-console' end
  24. 第4 章: ルーティングでの美しさ Rails.application.routes.draw do # 認証関連 devise_for :users root

    'home#index' # 管理者機能 namespace :admin do resources :users, only: [:index, :show, :destroy] resources :orders, only: [:index, :show] end # API (バージョン管理) namespace :api do namespace :v1 do resources :users, only: [:show, :update] resources :orders, only: [:index, :create, :show] end end # 公開ページ resources :products, only: [:index, :show] resources :orders, only: [:new, :create, :show] end
  25. 第4 章: マイグレーションでの美しさ class CreateOrders < ActiveRecord::Migration[7.0] def change create_table

    :orders do |t| # 外部キー t.references :user, null: false, foreign_key: true t.references :product, null: false, foreign_key: true # 基本情報 t.string :status, null: false, default: 'pending' t.integer :quantity, null: false # 金額情報 t.decimal :unit_price, precision: 10, scale: 2, null: false t.decimal :total_amount, precision: 10, scale: 2, null: false # タイムスタンプ t.timestamps end # インデックス add_index :orders, :status add_index :orders, [:user_id, :created_at] end end
  26. 第4 章: テストでの美しさ RSpec.describe User, type: :model do # セットアップ

    let(:user) { create(:user) } # アソシエーション describe 'associations' do it { should belong_to(:company).optional } it { should have_many(:orders) } end # バリデーション describe 'validations' do it { should validate_presence_of(:email) } it { should validate_uniqueness_of(:email) } end # メソッド describe '#full_name' do it 'returns the full name' do user = create(:user, first_name: 'John', last_name: 'Doe') expect(user.full_name).to eq('John Doe') end end end
  27. コメントすべきでは「ない」事 コードからすぐに抽出できること 酷いコード (ex. ひどい名前の関数) を補う補助的なコメント コメントすべき事 コードを書いている 自分の考え を記録

    全体像のコメント 適切に分割した上で、ある程度複雑な処理を行なっている関数やクラス・ファイルなどに対して高レベ ルな要約コメントがあると、新しく参加したチームメンバーの理解が助かる 記録すべき自分の考え なぜコードが他のやり方ではなくこうなっているのか?( 監督コメンタリー) コードの欠陥をTODO: やXXX: などの記法を使って示す 定数の値にまつわる「背景」 どのような根拠で値が決まっているのかわからない場合がある
  28. 第5 章: 全体像のコメント 複雑な処理の高レベル要約 ポイント: 新メンバーが理解できる程度の要約のみ。詳細はメソッド名で表現 class OrderFulfillmentProcessor # 注文処理:

    在庫確保 → 決済 → 配送 → 通知 def process_order(order) ActiveRecord::Base.transaction do reserve_inventory(order) process_payment(order) schedule_shipping(order) send_confirmation_email(order) end rescue StandardError => e rollback_order_processing(order, e) raise end end
  29. 第5 章: 技術的制約のコメント 一時的対応と将来計画を明記 ポイント: HACK (一時対応)とTODO (将来計画)のみ記録 class OrderFulfillmentProcessor

    private def reserve_inventory(order) # HACK: 悲観的ロックで競合回避(v3.0 で在庫サービス分離予定) order.line_items.each do |item| item.product.with_lock { item.product.reserve!(item.quantity) } end end def process_payment(order) # TODO: PayPal 対応(v2.1 予定) StripePaymentService.new(order).charge! end end
  30. 第5 章: 自分の考えを記録する なぜこのコードになったのか(監督コメンタリー) ポイント: 「なぜそうしたのか」の理由を簡潔に明記。将来の変更時に判断材料となる class UserRegistrationService def initialize(user_params)

    @user_params = user_params # TODO: v2.0 でメール確認機能を実装 @skip_email_confirmation = true end def call # cost=10: セキュリティとパフォーマンスのバランス User.create!(@user_params.merge( encrypted_password: BCrypt::Password.create(password, cost: 10) )) end end
  31. ✅ より実用的な例 重要: コメントは「なぜ」に焦点を当て、 「何を」はコードで表現 class UserService def calculate_loyalty_points(user) #

    ベータ版期間中は全ユーザー1000pt 付与(2024/3/31 まで) return 1000 if Rails.env.production? && Date.current < Date.new(2024, 3, 31) user.orders.where(status: 'completed').sum(:amount) * 0.01 end def send_notification(user, message) # 法的要件: GDPR 対応のため明示的同意ユーザーのみ return unless user.notification_consent_given? NotificationMailer.send_to(user, message) end end
  32. 第5 章: コードの欠陥をマークする TODO / FIXME / HACK の適切な使い分け ポイント:

    TODO (実装予定) 、HACK (一時対応) 、FIXME (問題箇所)のみ簡潔に class PaymentGateway # TODO: PayPal 対応(v2.1 予定) SUPPORTED_PROVIDERS = ['stripe'].freeze def process_payment(amount, provider) case provider when 'stripe' # HACK: webhook 重複対応(一時的) idempotency_key = generate_idempotency_key(amount) Stripe::Charge.create(amount: amount, idempotency_key: idempotency_key) else # FIXME: 適切なエラーハンドリング必要 raise NotImplementedError, "#{provider} is not supported yet" end end end
  33. 第5 章: 定数の値にまつわる背景 なぜその値を選んだのかを記録 ポイント: 外部制約や制限に関わる重要な定数のみコメント class OrderService # Stripe

    API 制限: 10 秒でタイムアウト PAYMENT_TIMEOUT_SECONDS = 10 # 一括処理上限(メモリ制限考慮) BATCH_SIZE = 100 MAX_RETRY_COUNT = 5 # デフォルト値のため説明不要 end
  34. 第6 章: 情報密度を高める 悪い例 ❌ - 情報密度が低い class OrderService #

    この値は注文の処理において、システムが外部のAPI を # 呼び出す際に、応答を待つ時間の上限を設定している TIMEOUT_SECONDS = 30 # このメソッドは引数として渡された注文オブジェクトに対して # 様々な処理を実行します def process_order(order) # 処理... end end
  35. 良い例 ✅ - 高い情報密度 ポイント: 冗長な説明を削除し、核心的な情報のみ記載 class OrderService # 外部API

    応答上限(Stripe 制限準拠) TIMEOUT_SECONDS = 30 # 注文確定: 決済→ 在庫引当→ 配送予約 def process_order(order) # 処理... end end
  36. 第6 章: 代名詞を避けて明確に 悪い例 ❌ - 「それ」 「これ」で曖昧 class EmailService

    def send_notification(user, message) # このメソッドではメール送信を行うのですが、その際にいくつかのチェックが必要になります。 # まず最初に、それがメール送信の同意をしているかを確認し、その後、これまでの送信回数についてもチェックが必要です。 # また、これについては時間帯の制限もあります。 # それが同意していない場合は送信しません return false unless user.email_consent? # これは1 日の送信制限をチェックする処理です # この制限については10 通までとなっています daily_count = user.emails_sent_today return false if daily_count >= 10 # それが夜間の場合は送信を控えます # この時間については22 時から7 時までです current_hour = Time.current.hour return false if current_hour >= 22 || current_hour < 7 MailerService.deliver(user.email, message) end end
  37. 良い例 ✅ - 簡潔で明確 ポイント: 「それ」 「これ」を具体的な対象を明記 class EmailService def

    send_notification(user, message) return false unless user.email_consent? # スパム防止:1 日10 通制限 daily_count = user.emails_sent_today return false if daily_count >= 10 # 夜間配信停止(騒音防止条例対応) current_hour = Time.current.hour return false if current_hour >= 22 || current_hour < 7 MailerService.deliver(user.email, message) end end
  38. 第6 章: 簡潔で意味のある表現 悪い例 ❌ - 冗長で曖昧な表現 class PaymentProcessor def

    process_payment(order) # この処理では決済を実行するのですが、その際にいくつかの手順を踏む必要があります。 # それが正常な注文かどうかを確認し、 # これをバッチ処理で実行することになります。 # それが重複していないかをチェックします return if order.processed? # これで実際の決済処理を実行します result = external_payment_api.charge(order.total) order.update!(status: 'completed', processed_at: Time.current) end end
  39. 良い例 ✅ - 簡潔で明確 ポイント: 冗長な説明を削除( 「この処理においては〜」 「〜する必要があります」→不要) 代名詞を排除( 「それ」

    「これ」→具体的な対象を明記) class PaymentProcessor def process_payment(order) return if order.processed? # 午前2 時実行(銀行システム競合回避) current_time = Time.current unless current_time.hour == 2 PaymentJob.perform_later(order.id) return end # 前回障害対策 sleep(30) result = external_payment_api.charge(order.total) order.update!(status: 'completed', processed_at: Time.current) end end
  40. 第10 章 汎用コードの分離 要約 プロジェクト固有のコードから汎用コードを分離する ほとんどのコードは汎用化できる 一般的な問題を解決するライブラリやヘルパー関数を作っていけば、プログラム固有の小さな核だけ残る ライブラリを使用する事も検討 (gem) やりすぎは良くない

    小さな関数を作りすぎてしまうと逆に読みにくくなってしまう 新しい関数をコードに追加すると、ごくわずか ( でも確実に) 読みにくさのコストが発生する プロジェクトの他の部分から使う必要が出た場合のみ切り出す事を考える
  41. 第10 章 汎用コードの分離 悪い例 ❌ - すべてが混在 class UsersController <

    ApplicationController def create @user = User.new(user_params) if @user.save # メール送信が直接書かれている mail = Mail.new do from '[email protected]' to @user.email subject ' ようこそ!' body "#{@user.name} さん、登録ありがとうございます" end mail.deliver redirect_to @user else render :new end end end
  42. 第10 章 汎用コードの分離 良い例 ✅ - 汎用コードを分離 # 汎用的なメール送信 class

    EmailService def self.send_welcome(user) mail = Mail.new do from '[email protected]' to user.email subject ' ようこそ!' body "#{user.name} さん、登録ありがとうございます" end mail.deliver end end
  43. 第10 章 汎用コードの分離 良い例 ✅ - プロジェクト固有のコードのみ ポイント: コントローラーはビジネスロジックのみに集中 class

    UsersController < ApplicationController def create @user = User.new(user_params) if @user.save EmailService.send_welcome(@user) redirect_to @user else render :new end end end
  44. 第10 章 汎用コードの分離 より簡単な例 # 悪い例:計算と表示が混在 def show_total_price(items) total =

    items.sum(&:price) puts " 合計:¥#{total.to_s(:delimited)}" end # 良い例:処理を分離 def calculate_total(items) items.sum(&:price) end def format_price(amount) "¥#{amount.to_s(:delimited)}" end
  45. 第11 章 一度に1 つのことを 要約 基本原則 1 つのメソッドは1 つのタスクだけを行う 複数のタスクが混在するメソッドを見つける方法

    メソッドの処理を日本語で説明してみる 「〜して、〜して、〜する」と複数の動作がある場合は分割対象 分割できるタスクは別メソッドに切り出す 分割できないタスクは論理的なまとまりでグループ化する 完璧な分割方法を考えるより、まず分割することが重要
  46. 第13 章 短いコードを書く 要約 鍵となる考え 最も読みやすいコードは、何も書かれていないコードだった 1. 不必要な機能をプロダクトから削除する。過剰な機能は持たせない 1. YAGNI

    原則: You Aren’t Gonna Need It 2. コードを小さく保つ 2. 最も簡単に問題を解決できるような要求を考える 3. 定期的にAPI を読んだり、標準ライブラリに慣れ親しんでおく