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

Code Smellsの Primitive Obsession に気を付けて設計する/Designing-with-Code-Smells-Primitive-Obsession

kirimaru
July 07, 2021

Code Smellsの Primitive Obsession に気を付けて設計する/Designing-with-Code-Smells-Primitive-Obsession

リーダブルコード LT会 - vol.2 #readablelt
での登壇資料。

kirimaru

July 07, 2021
Tweet

More Decks by kirimaru

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. 良いコードとは?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. 例えば、年度
    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
    型としての制約をつけると
    より表現ができます。

    View Slide

  11. 例えば、年度
    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チェック等は不要です。

    View Slide

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

    View Slide

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

    View Slide

  14. 例えば、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;
    }
    }

    View Slide

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

    View Slide

  16. 例えば、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;
    }
    }

    View Slide

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

    View Slide

  18. 例えば、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;
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. Appendix

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide