耐久!仕様・要求変更に耐える設計・開発 (with React)


はじめに

HCB Advent Calendar 2021
アドベントカレンダー9日目の記事になります。

本記事はここ一年間で得た、度重なる仕様・要求変更のある開発や大きい規模の開発で得た知見を記したものです。
以下の構成の記事となっています。
- 設計大事
- 仕様変更に強いコーディング
- 他人に見やすいコーディング

興味を持ってくれた方!
ゆ っ く り し て い っ て ね

設計大事

まずは設計について書きます。

なぜ初心者は設計やデザインをおろそかにするか?

会社で働いている方は「設計してないとかありえない」という感想を抱くと思います。
しかし、個人で開発していると設計とかしなくてよくない(wowow)?みたいなテンションで、デザイン案すら作らずに開発することが多いと思います(自分はそうでした)。
特に、そこまで大きくないシステムを作るときはありがちだと思います。

なぜ設計やデザインをおろそかにしてしまうのでしょうか?
理由としては以下のものがあげられると思います。

  • 面倒くさい
  • 時間がかかる
  • 設計したところで、特に設計した図面とかみない
  • デザインできないでござる
  • すぐに仕様変更するので作る気が失せる

もっともですね。特に一人でやる開発ではこう感じると思います。
では、設計やデザインによるメリットとは何でしょうか?

設計やデザインをする意義

設計やデザインをするメリットとしては以下のようなものが挙げられると思います。

  • 設計図やデザイン案を作ることで、何を作ればよいかが明確にわかり、共有できる
  • 認識というあいまいなモノではなく、実際に形として仕様が残り続ける
  • すぐにシステムについて確認することができる
  • 定義漏れを確認できる

こんなところでしょうか。
書き出してみるとわかるのですが、他の人との共有や、作り終わった後でメリットを感じることができる内容が多いと思います。
まさに初心者を脱してから初めてありがたみを知ることができる内容となっていますね。

では、デザインをしないデメリットは何でしょうか。

  • 作るべきシステムの全体像や仕様は、開発者の数だけ、開発者の脳内にしかなく、形あるものとして残らないので忘れたら THE END
  • システムの仕様を確認するときは、他の人に尋ねるか(正しいとは限らない)、数千、数万行のコードやファイルを紐解くことで把握しなおさなければいけない
  • 他の人に全体像や仕様を共有できない
  • 全体像や仕様を見直すための資料がないので、開発前に問題がある場所を探すことが困難

はい、恐ろしいですね。
メリットだけ見るとそんなにいいことある?と思うかもしれませんが、デメリットを見ると重要性が分かると思います。
チーム開発で設計やデザインをしなかったら、と考えると自分にはデスマーチやメテオフォール、多方面からの時限式爆撃が脳内に鮮明に浮かんできます。

何をすればいいの?

デザインや設計の重要さが分かったとはいえ、何をすればいいかわからないという方もいると思います (自分はそうでした)。
ggrksというのは簡単ですが、ここでは自分がこの一年間でやってみたものを紹介します。

個人的にはですが、初心者が一人でもできる設計的な方法としては「ユーザーストーリーマップ・MVPの作成」、「画面デザインの作成」、「画面構成とデータの受け渡し方の設計」を順にやるといいと思います。

ユーザーストーリーマップ・MVPの作成

「ユーザーストーリーマップ・MVPの作成」では何をするかを簡単に説明すると、使用想定ユーザーがどのような流れでサービスを利用するのかを図のような形で時系列順に書き記していき、各時間ごとの使い方でどのような機能が必要かを書き落としていきます。
これをすることによって、「サービスの使い方、必要な機能」を洗い出し、定義漏れを防いだり、作ろうとしているものの全体像を他の人と共有することができます。かなり説明はざっくりとしたものとなっているので、ユーザーストーリーマップやMVPについて調べてみることをお勧めします。

画面デザインの作成

これはそのまんまで、作ろうとしているサービスの画面をデザインしていきます。これをすることで、サービスの具体的なイメージや、設計上での留意点 (データの流れや、システムの挙動、必要なデータなど) などを洗い出すことができます。画面をデザインする際、ボタンの挙動や、リストの挙動など、UIパーツを操作した時のデザインの変化も一緒に作成したほうがいいです。デザインの変化の存在を忘れると、後で定義漏れが発覚し、作業のやり直しが発生します。また、デザインと一緒に画面内で扱うデータを洗い出し、デザインの横に記しておくことをお勧めします。これをすると何が良いかというと、サーバーとフロントでやり取りするデータの認識をすり合わせることができるからです。コーディング段階でモックを作った後に、「あ、このデータサーバーから受け取れなくない?」となった時の絶望感はすさまじいです。

画面構成とデータの受け渡し方の設計

「画面構成とデータの受け渡し方の設計」では、作成するべき画面やパーツとその中で使われる変数の定義、ページ間でのデータの流れや受け渡し方、サーバーやデータベース(内部含む)とのデータのやり取りを定義します。開発前にこれをすることによって、開発中における内部構造やロジックの大幅な変更を防ぐことができます。また、処理が重そうな部分などの問題点も洗いだすことができます。




仕様変更に強いコーディング

ここでは度重なる仕様変更や要求変更に対して自分が試行錯誤して対策した、コーディングする上での方法を書いていきます。

仕様変更やや要求変更によって一番影響が出るものは何でしょうか?
私はずばりデータ(変数)だと考えます。
「このデータをこの画面で表示させたい」、「このデータ必要だった」などの言葉によって変数がPONPON増えたり減ったり、変わったりします。
なので、自分はこの変数がPONPON変わる、という自然現象に対して、対策を練ることにしました。
自分が行っている対策は以下です。

  • 型をつける
  • 変数の呼び出し方、利用の仕方を柔軟にする

型をつける

はい、自分は天才ではないので型付き言語以外触りたくないマンです。
型をつけることによって次のメリットが生じます。

  • 変数の変更があった時に、影響範囲を知ることができる
  • 定義漏れ・定義間違えがあればすぐに気づける

素晴らしいですね。
仕様変更によって変数が変わった場合型の宣言を変更してあげるだけでその型を使っている部分、つまり影響範囲上でワーニングやエラーが出るので、仕様変更による書き換え作業がぐっと減ります。特にフロントの場合、書き方によってはサーバーとの通信内容に変更があっても型の宣言部分と、UIパーツ上(コンポーネント)の反映場所の計2か所書き換えるだけで済むようになります。ビバ型付け言語様。

型を利用したコードの例:
// 元の型
export type MessageType = {
    text: {
      sender: string,
      comment: string
    }
    imgUrl: string,
    graph:ReceiveMessageGraphType[] | null
}

// 変更前のコンポーネント
const Message: React.FC<MessageType> = (props) => {
  const { text, imgUrl, graph } = props
  return (
    <Wrapper >
      <Grid
        columns={["66px", "1fr"]}
        rows={
          [
            "auto"
          ]
        }
        areas={[
          ["icon", "content"],
        ]}>
        <GridArea name="icon">
          <Image
            url={imgUrl}
            width={50}
            height="auto"
            isFitContent={true}
            shape='ROUND' />
        </GridArea>
        <GridArea name="content">
          <HeadContainer>
            <Text text={text.sender} style={{ fontSize: "Normal", fontColor: 'Black', bold: true}} />
            <Text text={text.time} style={{ fontSize: "Normal", fontColor: "Black" }} />
          </HeadContainer>
          <Text text={text.comment} style={{ fontSize: "Normal", fontColor: 'Black' }} />
        </GridArea>
      </Grid>
      <Graph data={graph}/>
    </Wrapper>
  );
}

// 変更後の型
export type MessageType = {
    text: {
      sender: string,
      time: string,  <=変更箇所
      comment: string
    }
    imgUrl: string,
    graph:ReceiveMessageGraphType[] | null
}

// 変更後のコンポーネント
const Message: React.FC<MessageType> = (props) => {
  const { text, imgUrl, graph } = props
  return (
    <Wrapper >
      <Grid
        columns={["66px", "1fr"]}
        rows={
          [
            "auto"
          ]
        }
        areas={[
          ["icon", "content"],
        ]}>
        <GridArea name="icon">
          <Image
            url={imgUrl}
            width={50}
            height="auto"
            isFitContent={true}
            shape='ROUND' />
        </GridArea>
        <GridArea name="content">
          <HeadContainer>
            <Text text={text.sender} style={{ fontSize: "Normal", fontColor: 'Black', bold: true}} />
=> 変更箇所    <Text text={text.time} style={{ fontSize: "Normal", fontColor: "Black" }} />
          </HeadContainer>
          <Text text={text.comment} style={{ fontSize: "Normal", fontColor: 'Black' }} />
        </GridArea>
      </Grid>
      <Graph data={graph}/>
    </Wrapper>
  );
}

変数の呼び出し方、利用の仕方を柔軟にする

データの型や種類自体は変わらないけど、定数が要求の変更によって変わることがあると思います (自分はありました)。
定数が増えるたび面倒な記述が増えると面倒くさいので、変数の呼び出し方を工夫することで、要求の変更に強いコードにします。
変数の宣言は面倒くさくなりますが、UI周りや、データの取り回しが格段に楽になる (当社比) のでお勧めです。

変更に対応しやすくしたコードの例:
export const TimeZone = {
    DayTime:"dayTime",
    Evening:"evening",
    Night:"night"
} as const

export const PlaceName = {
    meetingWindow:"meeting_window",
    meetingEnter:"meeting_enter",
    meetingSmall:"meeting_small",
    officeWindow:"office_window",
    officeCenter:"office_center",
    officeEnter:"office_enter",
    passage:"passage"
}

export const WeatherTypeKey: {[key:number]: keyof typeof Weather} = {
    [0]: "Sunny",
    [1]: "Rain",
    [2]: "Cloud",
    [3]: "Snow"
}

export const TimeZoneTypeKey: {[key:number]: keyof typeof TimeZone} = {
    [0]: "DayTime",
    [1]: "Evening",
    [2]: "Night"
}

export const Weather = {
    Sunny: "sunnyColor",
    Cloud: "cloundColor",
    Rain: "rainColor",
    Snow: "snowColor"
}

type styleColorWeatherTime ={
    dateBox: string,
    box:string,
    bg: string,
    loadLine: string
}

export type styleColorWeather = {
    [key:string]:styleColorWeatherTime,
    dayTime:styleColorWeatherTime,
    evening:styleColorWeatherTime,
    night:styleColorWeatherTime
}


export const styleColor: {[key: string]:styleColorWeather} = {
    [Weather.Sunny]: { // 晴れの日用
        dayTime: {
            dateBox: "#0265CC",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        evening: {
            dateBox: "#7D4F40",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        night: {
            dateBox: "#1E2A2D",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
    },
    [Weather.Cloud]: { // 曇りの日用
        dayTime: {
            dateBox: "#7C7C8E",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        evening: {
            dateBox: "#0265CC",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        night: {
            dateBox: "#0265CC",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
    },
    [Weather.Rain]: { // 雨の日用
        dayTime: {
            dateBox: "#3A86A2",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        evening: {
            dateBox: "#21557D",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        night: {
            dateBox: "#292A45",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
    },
    [Weather.Snow]: { // 雪の日用
        dayTime: {
            dateBox: "#7EBED7",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        evening: {
            dateBox: "#2E547E",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
        night: {
            dateBox: "#0F2244",
            box:"#4A87B2",
            bg: "#00000080",
            loadLine: "#E69900"
        },
    },
}

他人に見やすいコーディング

共同開発をするようになると、他人を意識したコードを書く必要があります。
それは、無駄な確認作業を省いたり、間違ったコードの使い方を防いだりするためであったりと様々な理由があります。
近年だと、コードそのものが設計書として扱われるようになったので、なおさら他人が見やすいコードを書く必要があると思います。

他人の人が見やすいコードとは何でしょうか?
自分が他の人のコードを見るときに知りたい情報を考えてみると、以下のようなものが上がります。

  • 記述されている関数やクラスは何をするものなのか
  • 記述されている変数の意味は何か
  • コードの注意点 (必要であれば)
  • 何を想定して書いたか (必要であれば)

記述されている関数やクラスは何をするものなのか、記述されている変数の意味は何か

他人が書いたコードは基本的に1から読む必要があります。
しかし、コードの挙動を全て把握したいわけではありません。
変数や関数、クラスが何をするのかが把握できれば基本的に十分なことが多いです。

コーディングの際に変数や関数、クラスがどんな振る舞いをするかをわかりやすくすることで、理解を容易にする工夫が必要です。
それを実現するために最も気を付けなければいけないのが「名前」の付け方です。
aとかbとかつけるのではなく、isOpenButtonやFormatUNIXtoHourMinuteのように一目でどんな変数、あるいは関数なのかが分かるようにすると、それだけで他の人がコードを読むのにかかる時間を削減することができます。もちろん、自分がコードを読み直す際の時間や労力も削減することができます。

コードの注意点、何を想定して書いたか

一口にコーディングといっても、ソースコードを丁寧に書きさえすればよいというわけではありません。
コードファイルが設計書になりえる今、コメントも立派なコーディングの一つです。
コメントをつけることで、ソースコードだけではわからない、注意点や何を想定していたかというシステムの振る舞い以外の情報を伝えることができます。

最後に

以上、ここ一年間 (2021/5 ~ 2021/11)で学んできた、ノウハウを記事としてまとめました。
この一年間で一番の(ある意味)いい思い出は、2週間で3回も仕様・要求・デザインの変更が起こったことです。
自分はフロントの開発を一人で担当していたのですが、地獄でした w
この時、自分はこの記事に書いてあることを実践することで何とか乗り切ることができました。
とてもいい経験になりました(二度とあんな地獄は経験したくないですが)。

ここまで読んでくれた人は、ぜひLGTMを押してくさい。
喜びます。

明日のAdvent calendarは @KaitoKudou が記事を書くので、是非そちらも見てください。 m ( _ _ ) m


筆者:@takashinishikawapub
ブログ:https://creatorsfantasyfpn.com/tech_study/
github:https://github.com/takatakunishi
twitter:https://twitter.com/takashi54461358
wantedly:https://www.wantedly.com/id/takashi_nishikawa_24
一言:実務バイトしたいです (Webフロント)