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

1つのメソッドに、関心事が複数あると何が辛いのか?

Avatar for Yatakeke Yatakeke
June 06, 2025
630

 1つのメソッドに、関心事が複数あると何が辛いのか?

Avatar for Yatakeke

Yatakeke

June 06, 2025
Tweet

More Decks by Yatakeke

Transcript

  1. この発表の目的
 ターゲット
 ・モジュール設計の勘所が掴めていない開発者 
 ・ユニットテストの保守に苦労している開発者 
 
 アウトカム
 ・単一責任の原則を意識した設計が少しうまくなる 


    ・設計を意識することでテストの保守も楽になる 
 生成AIを使えばどうとでもなるのかもしれませんが、この発表のスコープには入っていません 
 2
  2. 今回のお題: 問題背景(gist)
 • あなたはとある工場の生産管理システムの開発者です。
 • 現在、あなたはそのシステムで顧客からの発注をもとに工場の製造品の生産計画 を作成する機能を作っています。
 • 工場の生産ラインは平日のみ稼働しています。(一旦、祝日は考慮しません)また、 工場の生産キャパシティは考慮する必要はありません。


    • あなたが実装する機能では、顧客からの注文(例、1-10日は150台納品)を稼働日 毎に均等に分割した計画を自動で作成します。
 • 発注数量は10日ごとにまとめて送られます。(例、2月11日から20日で155台納品し てほしい)
 • 端数は稼働日の初日から均等に割り振ってください
 5
  3. 今回のお題: データの例
 入力データ例 
 {
 "start_date": "2025-02-11", 
 "due_date": "2025-02-20",

    
 "quantity": 155
 }
 出力データ例 
 [
 {"date": "2025-02-11", "quantity": 20}, 
 {"date": "2025-02-12", "quantity": 20}, 
 {"date": "2025-02-13", "quantity": 20}, 
 {"date": "2025-02-14", "quantity": 19}, 
 {"date": "2025-02-17", "quantity": 19}, 
 {"date": "2025-02-18", "quantity": 19}, 
 {"date": "2025-02-19", "quantity": 19}, 
 {"date": "2025-02-20", "quantity": 19} 
 ]
 6
  4. 入力データ例 
 {
 "start_date": "2025-02-11", 
 "due_date": "2025-02-20", 
 "quantity":

    155
 }
 出力データ例 
 [
 {"date": "2025-02-11", "quantity": 20}, 
 {"date": "2025-02-12", "quantity": 20}, 
 {"date": "2025-02-13", "quantity": 20}, 
 {"date": "2025-02-14", "quantity": 19}, 
 {"date": "2025-02-17", "quantity": 19}, 
 {"date": "2025-02-18", "quantity": 19}, 
 {"date": "2025-02-19", "quantity": 19}, 
 {"date": "2025-02-20", "quantity": 19} 
 ]
 今回のお題: データの例
 7 端数は初日から割り当て 
 155/8 = 152…3

  5. 入力データ例 
 {
 "start_date": "2025-02-11", 
 "due_date": "2025-02-20", 
 "quantity":

    155
 }
 出力データ例 
 [
 {"date": "2025-02-11", "quantity": 20}, 
 {"date": "2025-02-12", "quantity": 20}, 
 {"date": "2025-02-13", "quantity": 20}, 
 {"date": "2025-02-14", "quantity": 19}, 
 {"date": "2025-02-17", "quantity": 19}, 
 {"date": "2025-02-18", "quantity": 19}, 
 {"date": "2025-02-19", "quantity": 19}, 
 {"date": "2025-02-20", "quantity": 19} 
 ]
 今回のお題: データの例
 8 端数は初日から割り当て 
 155/8 = 152…3
 15,16日は土日のため 
 スキップ
 要約
 
 ・10日ごとに数百台の注文がくる 
 ・それを工場の作業日ごとに分割しなさい 
 ・ただし、作業日は平日 

  6. 実際に若手が書いていたコードの紹介(gist)
 public class TenDaysOrderCalculator { 
 public List<ProductionPlan> dailySplitBy(TenDaysOrder tenDaysOrder)

    { 
 // 次ページ参照 
 }
 public Integer countWeekDays(LocalDate targetDate) { // 次々ページ参照 } 
 private boolean isWeekDay(LocalDate date) {
 return !(date.getDayOfWeek() == DayOfWeek.SATURDAY 
 || date.getDayOfWeek() == DayOfWeek.SUNDAY); 
 }
 }
 10
  7. countWeekDays
 public Integer countWeekDays(LocalDate targetDate) {
 var count = 0;


    var date = targetDate;
 for (int i = 0; i < 10; i++) {
 if (targetDate.getMonth() != date.getMonth()) { break; }
 if (isWeekDay(date)) { count += 1; }
 date = date.plusDays(1);
 }
 return count;
 }
 11
  8. このとき起きていた辛さ ~ユニットテスト編~
 dailySplitBy(TenDaysOrder) 
 CountWeekDays(LocalDate) 
 ・1月11日旬の数量が109だった場合_ 
  13日の日次が19になりそれ以外が18になる 


    
 ・2月21日旬の数量が5だった場合_ 
  28日の日次が0になりそれ以外が1になる 
 ・2025年1月11日旬の場合_平日の数が6になる 
 
 ・2026年2月21日旬の場合_平日の数が5になる 
 15
  9. このとき起きていた辛さ ~ユニットテスト編~
 ・1月11日旬の数量が109だった場合_ 
  13日の日次が19になりそれ以外が18になる 
 
 ・2月21日旬の数量が5だった場合_ 
  28日の日次が0になりそれ以外が1になる

    
 ・2025年1月11日旬の場合_平日の数が6になる 
 
 ・2026年2月21日旬の場合_平日の数が5になる 
 稼働日の算出と数量の割り当て の2つの目的があるので、 
 テストケースに 
 組み合わせ爆発が起きている 
 
 →ケース洗い出しが複雑化 
 →簡易的で似たようなケースを 使いがち 
 16
  10. 今後起こりうる辛さ ~仕様変更編(ユニットテスト)~
 1週間後に以下の仕様変更が入りました(と、しましょう)
 → 「平日を稼働日として扱うのではなくて、カレンダーデータをDBに保持するのでその 情報を稼働日にしてほしいです。」
 dailySplitBy(TenDaysOrder) 
 CountWeekDays(LocalDate) 


    ・1月11日旬の数量が109だった場合_ 
  13日の日次が19になりそれ以外が18になる 
 
 ・2月21日旬の数量が5だった場合_ 
  28日の日次が0になりそれ以外が1になる 
 ・2025年1月11日旬の場合_平日の数が6になる 
 
 ・2026年2月21日旬の場合_平日の数が5になる 
 18
  11. 今後起こりうる辛さ ~仕様変更編(ユニットテスト)~
 1週間後に以下の仕様変更が入りました(と、しましょう)
 → 「平日を稼働日として扱うのではなくて、カレンダーデータをDBに保持するのでその 情報を稼働日にしてほしいです。」
 dailySplitBy(TenDaysOrder) 
 CountWeekDays(LocalDate) 


    ・1月11日旬の数量が109だった場合_ 
  13日の日次が19になりそれ以外が18になる 
 
 ・2月21日旬の数量が5だった場合_ 
  28日の日次が0になりそれ以外が1になる 
 ・2025年1月11日旬の場合_平日の数が6になる 
 
 ・2026年2月21日旬の場合_平日の数が5になる 
 修正範囲は全部
 19 関心事が混在するとテストの修正が辛くなることもある 

  12. なぜこうなってしまったのか
 このTodoがそのままメソッドに反映されていた
 
  [ ] 平日かどうかを判定する
   → isWeekDay
  [ ]

    指定された10日間の平日の数を計算する
   → countWeekDay
  [ ] 指定した10日間と数量を渡すと日バラシにする
   → dailySplitBy
 23
  13. なぜこうなってしまったのか
 このTodoがそのままメソッドに反映されていた
 
  [ ] 平日かどうかを判定する
   → isWeekDay
  [ ]

    指定された10日間の平日の数を計算する
   → countWeekDay
  [ ] 指定した10日間と数量を渡すと日バラシにする
   → dailySplitBy
 やりたいのは ここ
 24
  14. なぜこうなってしまったのか
 このTodoがそのままメソッドに反映されていた
 
  [ ] 平日かどうかを判定する
   → isWeekDay
  [ ]

    指定された10日間の平日の数を計算する
   → countWeekDay
  [ ] 指定した10日間と数量を渡すと日バラシにする
   → dailySplitBy
 やりたいのは ここ
 重要そうでテストが書きやすそうな 
 メソッドで切り分けていた
 25 考えやすそうと変更しやすいは違う 

  15. より良い設計のために問題設定をふりかえる
 • あなたはとある工場の生産管理システムの開発者です。
 • 現在、あなたはそのシステムで顧客からの発注をもとに工場の製造品の生産計画 を作成する機能を作っています。
 • 工場の生産ラインは平日のみ稼働しています。(一旦、祝日は考慮しません)また、 工場の生産キャパシティは考慮する必要はありません。
 •

    あなたが実装する機能では、顧客からの注文(例、1-10日は150台納品)を稼働日 毎に均等に分割した計画を自動で作成します。
 • 発注数量は10日ごとにまとめて送られます。(例、2月11日から20日で155台納品し てほしい)
 • 端数は稼働日の初日から均等に割り振ってください
 26
  16. 問題設定を構造化してみる
 生産計画を作成する
 ・入力は顧客からの注文データ 
  ・平日と休日に関係ない10日毎の台数 
 ・稼働日毎に均等に分割した生産計画を出力する 
  ・工場が稼働する日の計算 


      ・工場の生産ラインは平日のみ稼働している
   ・祝日は考慮しなくても良い
  ・稼働日ごとの生産量の計算 
   ・合計台数を稼働日から均等に割り振る
   ・端数は初日から均等に割り振る
 2月11日から20日で155台 
 11,12,13,14, , ,17,18,19,20日 
 20,20,20,19, , ,19,19,19,19個 
 27
  17. 問題設定を構造化してみる
 生産計画を作成する
 ・入力は顧客からの注文データ 
  ・平日と休日に関係ない10日毎の台数 
 ・稼働日毎に均等に分割した生産計画を出力する 
  ・工場が稼働する日の計算 


      ・工場の生産ラインは平日のみ稼働している
   ・祝日は考慮しなくても良い
  ・稼働日ごとの生産量の計算 
   ・合計台数を稼働日から均等に割り振る
   ・端数は初日から均等に割り振る
 2月11日から20日で155台 
 11,12,13,14, , ,17,18,19,20日 
 20,20,20,19, , ,19,19,19,19個 
 ポイントはこのインターフェース
 28
  18. 役割が分離できた設計
 interface WorkingDaysCalculator { 
 public List<OrderDate> calculate(
 OrderDate orderDate

    
 );
 }
 interface OrderQuantityAllocator { 
 public List<ProductionPlan> allocate(
 List<OrderDate> workingDays, 
 OrderQuantity quantity 
 );
 }
 与えられた日にちから
 稼働日を割り出す
 稼働日のリストに対して数量を
 均等に割り当てる
 30
  19. そのとき自分が書いたコード
 interface TenDaysOrderCalculator {
 public List<OrderDate> calculateWorkingDays(
 OrderDate orderDate
 );


    public List<Map<OrderDate, OrderQuantity>> allocateQuantity(
 List<OrderDate> workingDays,
 OrderQuantity quantity
 );
 }
 37