Slide 1

Slide 1 text

Code Smellsの Primitive Obsession に気を付けて設計する リーダブルコード LT会 - vol.2 #readablelt 20210707 きり丸(水上 皓登)@nainaistar

Slide 2

Slide 2 text

※ Javaのソースコードで例示します。 また、今回の話は型の話をするので、 Python等の動的型付言語では メリットが薄いかもしれません

Slide 3

Slide 3 text

名前:きり丸(水上 皓登) twitter:nainaistar GitHub:hirotoKirimaru ブログ:きり丸の技術日記 https://nainaistar.hatenablog.com/ 3 リファクタリングの 正解がわからない

Slide 4

Slide 4 text

良いコードとは?

Slide 5

Slide 5 text

良いコードとは悪くないコード

Slide 6

Slide 6 text

悪くないコードにするには、 Code Smellsに気を付けたらいい

Slide 7

Slide 7 text

Primitive Obsessionとは (基本データ型の執着) 言語に用意されているデータ型しか使わないことを指します。 用意されているデータ型だけしか使用していると、 どんなデータが入るか表現できません。 コードを読む際に、業務知識が必要になるコードもあります。 自分たちが作成した型を用意することで、 よりプログラムの表現力を高めることができます。

Slide 8

Slide 8 text

例えば、年度 現在は、西暦2021年度です。 下2桁の21年度だけ表現することもあります。 令和3年度とも言います。R3と表現するかもしれません。 今年度の期間という表現をした場合は、 学校であれば「2021/04/01-2022/03/31」ですし、 6月が期末の会社であれば「2021/07/01-2022/06/30」です。

Slide 9

Slide 9 text

例えば、年度 public class 年度{ private final String value; public 年度(String value){ this.value = value; } } 単純に型をラップするだけでも 意図を表現できます。

Slide 10

Slide 10 text

例えば、年度 public class 年度{ // 2021等の4桁のみを許容する private final String value; public 年度(String value){ if (value.length != 4){ throw new RuntimeException(“4桁以外”); } this.value = value; } } // もし、日付を文字列で管理していたら…? // ISO8601基本形式? 20210627T112445+0900 // ISO8601拡張形式? 2021-06-27T11:24:45+09:00 // 独自形式? 2021/06/27 11:24:45 // もっと独自形式? 2021/06/27|11:24:45 型としての制約をつけると より表現ができます。

Slide 11

Slide 11 text

例えば、年度 public class 年度{ private final String value; public 年度(String value){ if (value.length != 4){ throw new RuntimeException(“4桁以外”); } this.value = value; } // 2021 -> 21 public String toShortYear() { return this.value.substring(2); }   // 年度をLocalDateTimeへ変換する public LocalDateTime toLocalDateTime(){ return LocalDateTime.of( Integer.parseInt(value), 1, 1, 0, 0 ); } } 制約を満たしている状態でイン スタンスを生成しているので、 Nullチェック等は不要です。

Slide 12

Slide 12 text

例えば、ISBN ISBNは本を一意に特定するためのIDです。 仕様: ● ISBNは10桁か13桁 ● 10桁から13桁に変換できるロジックがある ○ アプリ上は13桁で管理したいので、10桁は13桁に変換する ● ISBNは978, または979で始まる(現状の日本国内は978のみ) ○ 192は書籍JANコードの2段目を意味する

Slide 13

Slide 13 text

例えば、ISBN ISBNは本を一意に特定するためのIDです。 仕様: ● ISBNは10桁か13桁 ● 10桁から13桁に変換できるロジックがある ○ アプリ上は13桁で管理したいので、10桁は13桁に変換する ● ISBNは978, または979で始まる(現状の日本国内は978のみ) ○ 192は書籍JANコードの2段目を意味する

Slide 14

Slide 14 text

例えば、ISBN 欠落しがちな仕様を 型で表現することが できます (ISBNは10桁か13桁) public class Isbn { private final String isbn; public Isbn(String code) { // 10桁か13桁かチェック String len = code.length(); if (!(len == 10 || len == 13)) { throw new RuntimeException("エラー"); } String isbn1 = convert10to13(code); if (!”978”.equals(isbn1.substring(0, 3))) { throw new RuntimeException( “2段目バーコードを読み込んだ疑いあり” ); } this.isbn = isbn1; } }

Slide 15

Slide 15 text

例えば、ISBN ISBNは本を一意に特定するためのIDです。 仕様: ● ISBNは10桁か13桁 ● 10桁から13桁に変換できるロジックがある ○ アプリ上は13桁で管理したいので、10桁は13桁に変換する ● ISBNは978, または979で始まる(現状の日本国内は978のみ) ○ 192は書籍JANコードの2段目を意味する

Slide 16

Slide 16 text

例えば、ISBN 欠落しがちな仕様を 型で表現することが できます (10桁は13桁に変換する) public class Isbn { private final String isbn; public Isbn(String code) { String len = code.length(); if (!(len == 10 || len == 13)) { throw new RuntimeException("エラー"); } // 10桁から13桁に変換する(省略) // 13桁ならそのまま String isbn1 = convert10to13(code); if (!”978”.equals(isbn1.substring(0, 3))) { throw new RuntimeException( “2段目バーコードを読み込んだ疑いあり” ); } this.isbn = isbn1; } }

Slide 17

Slide 17 text

例えば、ISBN ISBNは本を一意に特定するためのIDです。 仕様: ● ISBNは10桁か13桁 ● 10桁から13桁に変換できるロジックがある ○ アプリ上は13桁で管理したいので、10桁は13桁に変換する ● ISBNは978, または979で始まる(現状の日本国内は978のみ) ○ 192は書籍JANコードの2段目を意味する

Slide 18

Slide 18 text

例えば、ISBN 欠落しがちな仕様を 型で表現することが できます (ISBNは978はじまり) public class Isbn { private final String isbn; public Isbn(String code) { String len = code.length(); if (!(len == 10 || len == 13)) { throw new RuntimeException("エラー"); } String isbn1 = convert10to13(code); // 本当にISBNか。 if (!”978”.equals(isbn1.substring(0, 3))) { throw new RuntimeException( “2段目バーコードを読み込んだ疑いあり” ); } this.isbn = isbn1; } }

Slide 19

Slide 19 text

例えば、山札・手札・場札 UNOを例にします。 今までの例から、札という型に変換することは思いつくでしょう。 しかし、山札・手札・場札等の配列の表現は 次のコードになっていないでしょうか。

Slide 20

Slide 20 text

例えば、 山札・手札・場札 public class UnoGame{ List<札> 山札; List<札> 手札; List<札> 場札; } public class 札{ String value; } 基本型のListを使っています。 これでは、表現を増やすのが 難しいです。

Slide 21

Slide 21 text

例えば、 山札・手札・場札 public class UnoGame{ 手札 手札; 山札 山札; 場札 場札; } public class 札 { String value; } public class 手札{ List<札> value; } public class 山札{ List<札> value; } public class 場札{ List<札> value; } 型がついたことにより、表現の 幅が広がりました。 また、操作するドメインが変わ り、メソッドがより適切な位置に 配備される可能性があります。

Slide 22

Slide 22 text

まとめ 業務知識でコードを読むのではなく、 コードに業務知識を表現させると可読性が上がります。 また、できる限り本物に近いデータを使用する必要があり、 異常データを防ぎやすくなるので、 異常検知が早くなる可能性があります。 データマッピングだけの型ではなく、 業務を表現した型を作ることで、 Code SmellsのPrimitive Obsessionを回避してみませんか?

Slide 23

Slide 23 text

Appendix

Slide 24

Slide 24 text

ブログ 基本型以外を使って設計レベルアップ! (Primitive ObsessionとFCC)

Slide 25

Slide 25 text

話すこと / 話さないこと ● Code Smells ● Primitive Obsession ● Java ● Primitive Obsession が有効ではない個所 (私もわからない) 詰めなおしは推奨されないので、 べき論が分からない 話すこと 話さないこと

Slide 26

Slide 26 text

対象者 / 非対象者 ● リファクタリングをしたいけど、 ヒントが分からない人 ● リファクタリングに興味が無い人 ● 動的型付言語 対象者 非対象者

Slide 27

Slide 27 text

登壇を見た人への期待するアクション ● Code Smellsに興味を持つ ● 型を作ることをやってみる アクション