$30 off During Our Annual Pro Sale. View Details »

自分がやった設計反省会 / Evaluation of my Architecture

自分がやった設計反省会 / Evaluation of my Architecture

社内勉強会用

数年前は最適な選択をしたつもりだったが、時が経つにつれツラミが増してきた設計について反省する会。
※ 誰かが悪いとかそういうのではなく、反省して次に活かそうという話です

ダーシノ

July 21, 2022
Tweet

More Decks by ダーシノ

Other Decks in Programming

Transcript

  1. 自分でやった設計反省会
    その節は、誠に申し訳ございませんでした。
    ダーシノ / @bc_rikko

    View Slide









  2.  

















     











    View Slide

  3. 失敗を共有する理由
    成功パターンはさまざまな要因が重なり再現性が低い
    失敗パターンはだいたい似ていて再現性が高い

    View Slide

  4. 目次
    TypeScript/JavaScript
    Enumの濫用
    型定義の誤った共通化
    大きすぎるライブラリ(lodash)の導入
    フレームワーク
    中途半端なコンポーネント化
    SFC内はJavaScriptで書く

    View Slide

  5. TypeScript/JavaScript

    View Slide

  6. Enumの濫用

    View Slide

  7. 当時の背景
    コード内にマジックナンバーや文字列リテラルがあった
    JavaScript → TypeScript の移行期だった
    if文、switch文などでtypoに気づけない
    定数クラスの代わりにString Enumsを採用した

    View Slide

  8. 何が悪かったのか
    定数を扱うために String Enums を採用
    冗長的で
    Enum 本来の使い方ではなかった
    Enum名がつくためコードが長くなった
    enum MyStatus {

    Waiting = 'waiting',

    Running = 'running',

    Finished = 'finished'

    }

    if (res.data.status === MyStatus.Running) {

    // ...

    }

    View Slide

  9. どうすればよかったのか
    Literal Union Types を使う
    マジックナンバーなど意味が伝わらりづらいときだけ
    Enum を使う
    as const でもOK
    // Literal Union Types

    type MyStatus = 'waiting' | 'running' | 'finished';

    if (status === 'running') { /***/ }

    // String Enums

    enum StatusCode {

    OK = 200,

    Forbidden = 403,

    NotFound = 404

    }

    if (res.code === StatusCode.NotFound) { /***/ }

    View Slide

  10. 型定義の誤った共通化

    View Slide

  11. 当時の背景
    JavaScriptで実装されたプロジェクトをTypeScriptに移行しようとして
    いた
    APIレスポンスの型定義を作りたかった
    ID ,
    Name ,
    Description , ... などはほとんど同じだった
    コード量を減らすために共通化した

    View Slide

  12. 何が悪かったのか
    APIの仕様上、エンドポイントによりスキーマが変わる
    ひとつにまとめたため、
    Optional Properties が大量発生した
    interface CommonResponse {

    id: string

    scope?: 'public' | 'private' // GET /item のときだけ有効

    status?: { availability: string } // GET /status のときだけ有効

    }

    const res: CommonResponse = await fetch(`/item/${id}`)

    console.log(`scope: ${res.scope}`) // scope: public

    // Cannot read properties of undefined

    if (res.status.availability === 'xxx') { /** */ }

    if (res.status?.availability === 'xxx') { /** */ } // Optional Chaining が必要

    View Slide

  13. どうすればよかったのか
    共通化するときは本当に同じモノなのか検討する
    Union Types を使う(
    Tagged Union Types も有効)
    type ItemResponse = {

    id: string

    scope: 'public' | 'private'

    }

    type StatusResponse = {

    id: string

    status: { availability: MyAvailable }

    }

    const res = await fetch(`/status/${id}`)

    if (res.status.availability === 'available') { /** */ }

    View Slide

  14. 大きすぎるライブラリの導入

    View Slide

  15. 当時の背景
    もともと使われていた
    Prototype.js の代用を探していた
    IE11やSafariの一部ブラウザで使えないコレクション操作メソッドがあった
    babel導入前でpolyfillという選択肢がなかった
    polyfillではなくlodashの導入を選択した

    View Slide

  16. 何が悪かったのか
    大きすぎてバンドルサイズがモリモリ増えた
    今となっては
    lodash がなくても実装できるようになった
    脆弱性報告がときどき来た
    import * as _ from 'lodash'

    _.includes(list, 'xxx')

    _.chain(list).map(...).flat(...).filter(...).values()

    _.get(item, 'a.b.c')

    View Slide

  17. どうすればよかったのか
    「あると便利だけどなくてもなんとかなる」ライブラリは徹底排除
    標準仕様を優先する
    過去のコードの真似をせず、最新の仕様で考える
    list.includes('xxx')

    list.map(...).flat(2).filter(...)

    item.a?.b?.c

    View Slide

  18. フレームワーク

    View Slide

  19. 中途半端なコンポーネント化

    View Slide

  20. 当時の背景
    Atomic Design の厳密性より、スピードと柔軟性を求めていた
    コンポーネント粒度が細かいほどバケツリレーになりがちだった
    よく使うコンポーネント以外は各ページで実装する

    View Slide

  21. 何が悪かったのか
    コンポーネント化する基準が曖昧なままチームメンバーが増えた
    同じスタイルを異なるページで使うようになった


    ...



    Lorem ipsum dolor sit amet

    consectetur



    <br/><br/>.mute { /***/ }<br/><br/>



    ...



    consectetur adipiscing elit

    sed do eiusmod



    <br/><br/>/** Foo.vueの .mute と同じスタイル */<br/><br/>.mute { /***/ }<br/><br/>

    View Slide

  22. どうすればよかったのか
    明確なガイドラインを用意する
    Atomic Design を採用する痛みはあれどプロジェクトは安定化する
    Atomic Design でなくてもチーム全体で合意がとれたガイドラインを用意す



    ...



    Lorem ipsum dolor sit amet

    ...





    ...



    consectetur adipiscing elit

    ...



    View Slide

  23. VueのSFCはJavaScriptで書く

    View Slide

  24. 当時の背景
    Nuxt@1 系(
    [email protected] )の頃は、TypeScritpのサポートが十分でなかった
    Class Components 化するよりはJavaScriptで書いたほうが悩みが少な
    いと思った
    JSDoc である程度型をつけられると思った
    SFC内はJavaScriptで実装した

    View Slide

  25. 何が悪かったのか
    Store 層はTypeScript化して
    dispatchs /
    getters にも型をつけた
    mapヘルパー や
    computed を使うことで
    Store 層の型が全部死んだ
    JSDoc を書かなかった
    export default {

    computed: {

    items() {

    return this.$store.getters['items'] || []

    }

    },

    methods: {

    doSomething() {

    const items = this.items // any

    }

    }

    }

    View Slide

  26. どうすればよかったのか
    TypeScriptのサポートが充実してきたころにJS→TSに移行する
    mapヘルパー の利用や
    getters を
    computed でラップしない
    export default Vue.extend({

    methods: {

    doSomething(): Promise {

    const items = this.$store.getters['items']

    // ^? Item[]

    }

    }

    })

    View Slide

  27. まとめポエム
    誰も悪くない。
    「最良の方法」だと信じていたし、実際そうだった。

    でも、時間が経つにつれ痛みが、 It's so painful.
    そして、過去の自分の設計を振り返ってこう思う。
    お前のいう「絶対」って60%くらいだよな、と。

    View Slide