mizzsugar’s blog

Pythonで学んだことや読書録を書きます。

【読書録】リーン・スタートアップ

技術書ではないですが、技術的なことを含め意思決定の判断軸になる話でした。

リーン・スタートアップ | エリック・リース, 伊藤 穣一(MITメディアラボ所長), 井口 耕二 |本 | 通販 | Amazon

読んでみようとした経緯

  • 仕事で感動させた人が2人もこの本の考えを仕事中に引用してたので、「これは読まないと!」と思ったこと。

概要

スタートアップで持続可能な事業を育てるための考え方や物事の進め方を紹介します。 この本での「スタートアップ」の定義は、組織の規模や立ち上げ時期に関係なく「とてもつなく不確実な状態で新しい製品やサービスを作り出さなければならない人的組織」(「リーン・スタートアップ」から引用)です。 不確実であるがゆえに計画通りにいかないけれども行きあたりばったりではうまくいかない状況でどう立ち向かうのか。 キーワードは「検証」と「学習」です。不確実な状況で創る新しい事業をどうやって持続可能にするかを「検証」によって「学習」します。


なぜ検証なのか?

従来のビジネスではウォーターフォール形式で物事を進めていました。ウォーターフォールが成り立つのは「これを実行したらこういう結果になる」と見えているからです。しかし、不確実な状況では予想外の出来事が多々起こり、最初に立てた計画通りに進みません。

ここで、ビジネスを前進させるための考え方として「検証」と「学習」が登場します。

スタートアップでは製品を世の中に出しても、上手く行くかどうか分かりません。ですから、製品を出すのがゴールではなく、出してみて顧客が同反応するかを実験する「検証」として捉えます。

実験で学んだことを踏まえて製品を改良するかそのまま進むか決めます。

この本では、検証と学習のサイクルを早く回すことが大事だといいます。

MVPとは何か?

MVPは製品デザインや技術的な問題を解決するためのものではない。基礎となる事業仮説を検証するためのものなのだ。

また、MVPに関してこんなツイートを見つけました。

MVPではデザインや技術的な問題を解決しないものの、検証対象となる価値を提供できるものでないといけないと学びました。

「リリースまでに最低限揃えないといけないもの」と今まで捉えていたのですが、「なぜリリースまでに揃えないといけないのか」のところを説明され、MVPで揃えるべき機能を判断する考え方が変わりました。

どういう仮設を検証したいか、検証のためにどんな価値を提供するかを考え、それをMVPの機能で実証出来るかを見ていきたいです。

正しく方向転換するために

検証したら、その結果を見て、成長するためには次何をするか決めなくてはなりません。その際に、正しく方向転換出来ないと、顧客が求めている価値を提供出来ず事業は停滞してしまいます。

正しい判断をするための基準として、「革新会計」という考え方が提唱されています。

売上や商談回数だけでなく、サイトの新規登録数・ダウンロード数・ログイン数などユーザーの行動を細かく分解してその結果を見ます。変更のたびにそれらの結果を見て

なぜその数字が改善・改悪されたのかを追う結果に対する要因が明確になり、舵取りの方向性も明確になります。

行動を促すような検証と行動を追うという考え方は、闇雲に何か変えようとなりにくく良いなと思いました。

スタートアップはスピードだけでなく開発の持続可能性も必要では?

エンジニアの仕事についてこんなことが書かれていました。

これに対してリーン・スタートアップの場合、たくさんのモノを効率的に作ることが目的ではない。持続可能な事業の構築方法をできるかぎり短時間で学ぶのが目的だ。
変化していく事業側のニーズに製品を対応させるのがエンジニアの仕事であり、事業に関する決定が正しいか否かにエンジニアは関与しないのだ。

これらを読んで、WEB+DB PRESS 113号の「体験ドメイン駆動設計」の一節を思い出しました。

 私たちはいつごろから片道ロケットの打ち上げ競争をやめて、飛行機を安定的に運行させたいと願うようになったのでしょうか。

(中略)
システム開発を取り巻く環境の変化は、片道ロケットに乗った開発者たちが返ってこなかったというのが大きな理由になっているのではないかと考えます。

 プログラムは動かすだけなら簡単で、しかし動かし続けるのは難しい代物です。開発速度を明大に形作られたソフトウェアはそのあとの運用について考慮されておらず、ビジネスの変化のたびにつぎはぎだあらけの応急処置が繰り返されて、複雑怪奇な進化を遂げます。

たしかに、保守運用のために回すよりもガンガン作った方が今目の前では利益を生むので、現時点での利益のためにこういったことに時間を費やすべきではないという意見もあります。

でも、持続させることを目的とするならば、持続可能なシステムを構築する手段としてテストやアーキテクチャに目を向けるべきではないかと思いました。「スタートアップだから」こういったことを考慮しないというのは、スタートアップの目的から考えると筋が合わないので、自分もここらへんの学習をより一層がんばります。

Python Charity Talks in Japan でLTしました。

Python Charity Talks in Japan とは

Python Software Foundation (略称 PSF) に100万円程度の寄付をすることを目的にしたイベントです。

pyconjp.connpass.com

PSFとは、Pythonの発展に尽力してくれている組織で、日本と関連することだとPyConJPを支援していたりします。

PSFはPyCon USの開催による収入をメインの収入源としていますが、COVID-19の影響で開催直前にオンライン開催となりました。これはPSFにとって財政的な大きな打撃となりました。

COVID-19がPSFの財政に与えた影響の深刻さはこの記事を読めばよくわかります。

thefortunate.blog

とても安心したLTのリハーサル

せっかくなのでLTに応募してみました。おかげさまで採択していただき、「変数に変数を代入したら?」という題で発表させていただきました。

Pyramidネタ、Djangoネタ、デプロイネタなど候補はいくつかありましたが、Webに限らず色んな分野の人が参加するイベントだろうなと思ったので

どの分野にも通ずる話にしました。また、タイトルだけを見て採択されるとのことだったので、タイトルだけで何について話そうとしているのか分かるように気をつけました。

speakerdeck.com

ZOOMでの発表なので、操作に不安がありましたが、リハーサルのおかげで本番ではトラブルなく無事5分以内で発表を終えることができました。

嬉しかった点
  • 登壇者用のマニュアルが用意されていたので本番前も見返せた
  • 操作についてSlackで気軽に質問出来た
  • 実際に画面共有して少し話したので、当日どういう操作すれば良いか迷わなかった
  • 声が小さい・スライドの文字が小さい等のフィードバックくれた
  • どのタイミングでZOOMに入り、音声をONにするかの案内は特に助かった

勉強になったトーク

PythonistaのためのコードレビューTips

スピーカーは araiさんです。

ライブラリが更新されたら自動でPRを作ってくれるdependabotを知らなかったので、Pycharity翌日に運用中の自作ブログに導入してみたら見逃していたバージョンアップがいくつかあり、助かりました。

requirements.txtだけでなくpyproject.tomlファイルでも利用でき、Poetryユーザーにも優しいです。

仕事でも導入しただけでとても感謝されて良かったです。

slideship.com

Python Software Foundation をより身近に感じたイベント

PSFは自分にとっては雲の上のような存在でした。しかし、このイベントを通して普段知らぬ間にPSFにお世話になっていることと、寄付を通してPSFすなわちPythonの発展を支えることが出来ると知りました。

自分が好きなもの(コーヒーとか本とか)は購入してサポートしていますが、プログラミング言語はあまりサポートのイメージがありませんでした。

自分が好きで使っているものこそ、ファンとして寄付を通してサポート出来たらと思います。

【読書録】失敗から学ぶRDBの正しい歩き方

この本は著者の方の経験から得たプラクティスが書かれていて実用的かつわかりやすくてとても勉強になりました!

Amazon CAPTCHA

読んでみようとした経緯

  • 良書だと聞いて
  • MySQLPostgreSQLで意識すべきところを知りたい

読むモチベーション

  • チーム開発で説得力のある根拠をもって話し合いたい
  • 自分は経験してない失敗を学んで同じ失敗をしないようにしたい

概要

MySQLPostgreSQLの違いに関して扱っているのは20章あるうちの2-3章くらいでした。他はどのRDBMSにも共通するようなテーブル設計、クエリ、運用のアンチパターンでした。

ただし、共通するアンチパターンの章の知識があるとよりMySQL, PostgreSQLの違いと付き合い方が理解できるので、良い構成だと思いました。

また、運用まで見据えているのは嬉しかったです。

読み終わって、自分の経験を振り返って、アンチパターンを踏んでしまっている人はみんな良かれと思ってやっているなあと思いました。良かれと思っているからこそ「○○という本に書いてあるアンチパターンも知らずにやっているあいつは勉強不足」と責めず、アンチパターンが良いと思った理由を聞きつつ、こちらがデメリットに感じている根拠を感情に任せず伝えると建設的だと思いました。また、振り返ると、アンチパターンでうまくいった経験があるからアンチパターンを踏むべきと思っている人が多かった気がします。ただし、その経験でのシステムと今取り扱っているシステムは違うので、過去の栄光は一回置いといて今求められていることと目の前のデータの中身を良くみないとアンチパターン踏むのが許容されるかどうかはわからないので気をつけたいです。


この本で特に学びになったこと

1. カーディナリティとフラグの闇

フラグがあるとINDEXがあまり効かない場合があります。その鍵は「カーディナリティ」です。この言葉を初めて知ったのですが、「列に格納されるデータの値にどのくらい種類があるのか?」の指標だそうです。種類が多い、つまり重複が少ないデータはカーディナリティが高いと言えます。カーディナリティが高ければ高いほどINDEXをうまく利用しやすいです。

この本では、カーディナリティが低い例として性別フラグを示しています。genderという列には 1= 男性 2 = 女性 という値しかありません。where gender = 1 のように絞り込むと検索結果が多くなりやすくINDEXがうまく活用できていません。

他にも、「削除フラグ」や「ユーザーの入会手続き中・アクティブ・退会済ステータス」「送信ステータス」などよくやってしまいそうな例を上げていました。(私も一つやってしまったけどまだ直せそうなので直したいです。)

何でもかんでも状態をもたせてはいけないというわけではなく、以下のものなら状態をもたせることが許容されます。

  • 対象テーブルが小さくINDEXが不要
  • そのテーブルに頻繁にJOINすることがない
  • UNIQUE制約が不要で、外部キーでデータの整合性を担保する必要がない


2. ORMに依存しないには

私は今、HTTPからORMまですべて揃っているWebフレームワークを使って開発しているのですが、使いにくさの原因が具体的に言語化されました。

テーブル設計がライブラリに依存していること、テーブル設計がViewに依存しやすいこと、ビジネスロジックがテーブル設計に引きづられることです。

ORMは完全にRDBMSに抽象化したものではなく、N+1問題やINDEXの効かないSQLを発行する恐れがあるので常にSQLを意識する必要があります。

その結果、ORMのモデルを直接ビジネスロジックに使うと、ビジネスロジックの中で、業務の感心ごととは関係ない、N+1問題を解決するための方法などSQLの都合が書かれていて書きづらいのだなと分かりました。

これに対して、「Modelの中でデータを取り出す層」(ORMのモデルを扱う層)と「取り出したデータを加工する層」(データのCRUD部分だけ行うリポジトリ層)を分けることが提案されています。

リポジトリ層では、こういったORMが抽象化しきれなかった部分も包みこみます。

モデルを直接ビジネスロジックに使わず完全に抽象化したリポジトリ層を使うことでビジネスロジックの中にSQLの都合を書かずに済んで良さそうです。

PHP製のフレームワークSynfonyはこの考え方を予め用意しているそうです。


3. MySQLPostgreSQLのロックの違い

MySQLPostgreSQLでのロックの範囲の違いから生じたシステムの不具合が紹介されていました。

この本の中で特にMySQLPostgreSQLの違いが強調されていたのはロックについてのように思えます。

PostgreSQLではSELECT文でも一番小さいレベルのロックを取られるとは知りませんでした。

明示的なロックでないと、このような前提知識がないとロックに気づかないのでちゃんとRDBMSのドキュメントを読んでSQL叩くサンドボックス環境作って実際に動きを確かめて学ばないとと身が引き締まりました。

html/CSSをESlintのルールに寄せてCI走らせた

背景

今作っているWEBサービスでは UI/UX(look and feelと言われるところ)を担当するデザイナーさんが、look and feelのデザインを考えHTML/CSSに落とし込みます。

JavaScriptで実際に動きをつける部分は別の人が担当します。※1

我々は、Nuxt.jsを使っているのでHTML/CSSファイルをvueファイルに置き換える必要があります。

この時、html/cssほぼコピペでvueに置き換えるので済むと楽です。

ただ、そうはいかない場合があります。

Nuxt.jsの部分はESlintを採用しており、ESlintを走らせるとhtml/cssの内容をコピペのままだと落ちる場合があります。

そこで、HTML/CSSにもLintを実行することにしました。


Linterは何にする?

htmlとCSSのLinterとして、htmllintcsslintがありました。

それらにしようと思いましたが辞めました。

ESlintのルールと同じではなく、ESlintに寄せるのが大変であるためです。

js-beautifyも考えましたが、あくまで整形するだけで注意はしてくれないのでCI向きではないなと諦めました。

ESlintと近いルールのLinterを探しましたが見つかりませんでした。

ESlintをhtml, cssファイルに走らせてみましたが、出来なかったので力技感はありますがhtmlをvueに置き換えてESlintを走らせることにしました。これだけいうと何言ってるか分かりませんので説明します↓


htmlからvueへ

CIで実現したいことを図にまとめるとこんな感じです。

f:id:mizzsugar:20200425161319p:plain

拡張子が.html, .cssのファイルの中身をコピーした.vueファイルを生成します。

といってもそのまま、.vueにするとsintax errorになるのでvueに合わせます。なお、vueファイルはCIを実行する際に生成されるもので、リポジトリには残りません。

生成はPythonスクリプトで実行します。Pythonの方が得意のなのでPythonにしていますが、nodeでも出来ると思います。


例↓

index.html (元)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bulma@0.8.1/css/bulma.min.css" rel="stylesheet" type="text/css">
    <link href="css/style.css" rel="stylesheet" type="text/css">
    <title>タイトル</title>
  </head>
  <body>
    <p>aaaaaaaaaaaaaaaaaaaa</p>
  </body>
</html>

index.vue (htmlを変換後)

<template>
  <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <link href="https://cdn.jsdelivr.net/npm/bulma@0.8.1/css/bulma.min.css" rel="stylesheet" type="text/css">
        <link href="css/style.css" rel="stylesheet" type="text/css">
        <title>タイトル</title>
    </head>
    <body>
        <p>aaaaaaaaaaaaaaaaaaaa</p>
    </body>
  </html>
</template>


style.css(元)

@charset "UTF-8";
/* CSS Document */

html{
  font-family: "Noto Sans Regular", "open-sans", YuGothic, "Yu Gothic medium", "Hiragino Sans", Meiryo, "sans-serif";
}

body{
  margin: 0;
  padding: 0;
}

style.vue (cssを変換後)

<template>
  <div />
</template>
<style>
@charset "UTF-8";
/* CSS Document */

html{
  font-family: "Noto Sans Regular", "open-sans", YuGothic, "Yu Gothic medium", "Hiragino Sans", Meiryo, "sans-serif";
}

body{
  margin: 0;
  padding: 0;
}
</style>


lint.py

import os
import pathlib


def _html_to_vue(target_file: pathlib.PosixPath) -> None:
    written = pathlib.Path(f'./_dest/{target_file}.vue')
    written.parent.mkdir(parents=True, exist_ok=True)
    with target_file.open() as r:
        with written.open(mode='w') as w:
            w.write('<template>\n')
            for line in r.readlines():
                if '<!doctype html>' in line:
                    continue
                if line == '\n':
                    w.write(line)
                    continue
                w.write(f'  {line}')
            w.write('</template>\n')  


def _css_to_vue(target_file: pathlib.PosixPath) -> None:
    written = pathlib.Path(f'./_dest/{target_file}.vue')
    written.parent.mkdir(parents=True, exist_ok=True)
    with target_file.open() as r:
        with written.open(mode='w') as w:
            w.write(
                '<template>\n'
                '  <div />\n'
                '</template>\n'
                '<style>\n'
            )
            for line in r.readlines():
                w.write(line)
            w.write('</style>\n')


def main() -> None:
    for html_file in pathlib.Path('.').glob('**/*.html'):
        _html_to_vue(html_file)

    for css_file in pathlib.Path('.').glob('**/*.css'):
        _css_to_vue(css_file)


if __name__ == '__main__':
    main()


次にCIを組みます。

Github Actionsを使います。

CIで起こることの手順はこうです↓

  1. Pythonスクリプトでhtml/cssをvueに置き換える

  2. 1で生成されたvueファイルに対してESlintが実行される

.github/workflows/main.yml

name: CI

on: [pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/cache@v1
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
      - run: python3 lint.py
      - run: npm set progress=false && npm ci
      - run: npm run lint


これでPRが提出されるたびにCIが走ってESlintのルールでhtml/cssにlintが実行されます。

じゃっかんhtml/cssファイルの行とずれていますが、nuxtで実装するときのESlintのルールがそのまま適用されるので置き換えが楽です。

サンプルリポジトリ

github.com

※1 この体制が良いか悪いかはこの記事の観点ではないのでご意見を受け付けません。ご了承ください。

【読書録】ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

前回の読書録で「現場で役立つシステム設計の原則」を扱い、その「この本と一緒に読むと面白そうな本」欄でこの本を書きました。「現場で役立つシステム設計の原則」を読んでからこの本を読みましたので、早速読書録を書き残したいと思います。

Amazon CAPTCHA

読んでみようとした経緯

  • DDDを理解して、分かりやすくて変更しやすいコードを書けるようになりたいから
  • エヴァンス本を理解出来なくて辛かったところ、この本が良いという噂を聞いたため
  • 著者の成瀬さんのDDDに関する講演がとても分かりやすくて面白かったので、本も読みたいと思ったため

読むモチベーション

  • 意図が伝わりやすいコードを書きたい
  • 適切な場所で適切な戦術的DDDの技法を使えるようになりたい
  • 戦術的DDDを実践するときになぜそれを実践したのか説明できるようになりたい

概要

ドメイン駆動設計をコードに落とし込むための手法「戦術的DDD」のうちいくつかのパターンを紹介しています。

最初は値オブジェクトから始まり、エンティティ、ドメインサービス、リポジトリ、アプリケーションサービス、集約…とだんだん抽象的になっていく構成です。

具体的でイメージのつきやすいものから始めてくれているおかげで、「途中から急に難易度があがって辛い!」ということがなくて読みやすかったです。

「本書の対象読者」の章にも書いてありますが、この本を読むには一般的なオブジェクト指向プログラミングの基礎知識が必要だそうです。

そういうこともあり、「現場で役立つシステム設計の原則」でオブジェクト指向プログラミングを学んでからこの本を読んだのは正解だったな〜と我ながら感心しています。

 

この本で特に学びになったこと

1. 値オブジェクトの考え方

値オブジェクトとは、システム固有の型のようなものです。

プログラミング言語レベルだとintegerやstringなどのプリミティブ型があります。でも、intやstringと定義しただけではどんな制約のあるのか分かりづらい時があります。例えば、作っているシステムで名前を扱う場合、名前は名字と名前から成り立っているというルールがあるとしましょう。'山田 太郎'というstring型でも表せます。しかし、これだとどこからどこまでが名字なのかわかりません。仮に' 'で区切ったとしても、'コウ ウラキ'のように名前と名字が逆で書かれる場合もあります。

そこで、名字と名前をもったオブジェクトにします。名字の属性と名前の属性をもった型を使用することで、それぞれの属性に値を渡せば'山田 太郎' 'コウ ウラキ'のような問題がなくなります。

 

「値オブジェクトは不変」という説明の意味も今までモヤっとしていましたが、integerやstirngの変数に「int_hoge.changeValue(1)」ようにできないのと同じ考えという説明は目から鱗でした。

また、Pythonで値オブジェクトを扱う時はfrozen=Trueにしたdataclassを使っていますがこれは間違っていないようで自信を持てました。pydanticを使う場合、allow_mutation=Falseにしたpydantic.BaseModelを継承したクラスでも良さそうです。

値オブジェクトを難しく考えていましたが、スッキリしました。

 

2. エンティティの考え方

エンティティと値オブジェクトの違いが良くわかっていなかったけれども、この本の説明はわかりやすいと思いました。

バリューオブジェクトのような値ではなく、変化されるオブジェクトです。10という数字は10という数字のままで、10.change_value()のようにはできません。社員オブジェクトのように部署や名前が変わりうるものはエンティティです。可変と不変がいまいちピンときていませんでしたが、納得できました。

また、社員オブジェクトの部署や名前は変わります。現実世界では、社員の名前が変わっても同じ人だと分かります。プログラムでも属性が変わっても同じと認識する必要があります。そのために同じだと認識させる属性を用意します。それがIDなどの一つしかないものになります。それを「同一性」と説明されるものだと分かりました。

何が可変なオブジェクトで何が不変かを整理すれば、不変であるべきオブジェクトを不変のままにでき、より正確なプログラムが出来そうだと思いました。

 

3. 凝縮度という観点

凝縮度という言葉を良く聞くけれども、よくわかっていませんでした。

凝縮度とはモジュールやクラスの責任範囲がどれだけ集中しているかを測る尺度です。凝縮度を高めると堅牢性、信頼性、再利用性、可用性の点で好ましいと言えます。凝縮度を具体的にどう測るかの計算式「LCOM」があります。端的に言うと、インスタンス変数はすべてのメソッドで使われるべきというものです。いくつかメソッドがあって、使われている変数と使われていない変数が多いほど凝縮度が低いと言えます。

 

その箇所を読んで、最近自分もレビューで似たようなことを言われてクラスを分けて実装したことを思い出しました。確かに、使われている変数が同じものほど同じ関心ごとを扱っていました。改めて整理すると、とある計算をする業務を扱うまとまりになったので計算を責務とするクラスに分けました。結果、計算を責務とするクラスは計算の内容の見通しが良くなりました。クラスだけでなくモジュールの見通しも良くなりました。文章に例えると、見出しが1つしかなかったのが内容ごとに見出しがついた感覚です。

その修正をしていた時は名前のない営みでしたが、あれが凝縮度を高くするという営みか、と凝縮度の話が身近に感じるようになりました。

 

4. ファクトリを使う判断

コンストラクタが複雑になった時にファクトリを使うと良く聞きますが、今までコンストラクタで事足りるケースばかりだったので具体的な判断基準がよく分かっていませんでした。

コンストラクタ内で別オブジェクトを生成するのはファクトリを使用する指標となると書かれていました。オブジェクトが変更される際にコンストラクタも変更しないといけない恐れがあり、複雑になる可能性が高いからです。

 

また、ファクトリの置き場も迷っていました。ファクトリ自体はドメインを表現していませんが、ドメインを表現するために必要であるからです。

生成対象となるオブジェクトのクラスと同じ階層に置くことで、俯瞰した時にひと目でファクトリを利用してオブジェクトを生成することを察することができます。また、同じ階層だけれどもドメインには入れないことでドメインモデルのオブジェクトはドメインルールの表現に集中できます。

 

この本と一緒に読むと面白そうな本

1. ユースケース駆動開発実践ガイド

読んだことないけど。「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」でもユースケースの話は出てきているので関連付けられそうだなあと思いました。

ユースケース図書かないと頭の中が混沌としたままでプログラム書けないのでそれらしきものは書いていますが、なんせ野良です。ちゃんとした書き方を知ってより分かりやすく書きたい&本に書いてある考え方で良いものがあれば取り入れたいです。

https://www.amazon.co.jp/%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E3%83%80%E3%82%B0%E3%83%BB%E3%83%AD%E3%83%BC%E3%82%BC%E3%83%B3%E3%83%90%E3%83%BC%E3%82%B0-ebook/dp/B01B5MX2TC/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E9%A7%86%E5%8B%95&qid=1585646662&s=digital-text&sr=1-1

【読書録】現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法

この本の読書会、途中で抜けちゃったけどちょくちょく読み進めていたので読書録残します。

www.amazon.co.jp

同じ読書会のメンバーのkassyiさんが毎回記録を残してくださったので こちらもぜひ。 読書会中に話題になった本に関連することもあって面白いです。

note.com

読んでみようとした経緯

  • 読書会で読むことになったため

読むモチベーション

  • 良書だと聞いて
  • オブジェクト指向設計を具体的にどんな考え方でどうコードに落とし込んでいるか、増田さんの考え方を知りたい
  • 意図が伝わりやすいコードを書きたい
  • 変更しやすいコードを書きたい

概要

オブジェクト指向プログラミング、オブジェクト指向設計を使って変更を安全にすばやくできるようにしようという趣旨です。 前半はコードを書く上での考え方やテクニックで、後半は全体的なアーキテクチャの話です。 プログラミング自体の初心者よりは、ある程度自力で書けるようになった人が次のレベルに行くといったレベル感です。


この本で特に学びになったこと

1. 具体的な条件分岐の書き方

この著書は前半だけでも読む価値が高いと思いました。

前半ではDDDでいうところの値オブジェクト、ドメインサービスの考え方と実装方法を具体的な業務システムの例と共に紹介しています。 エヴァンスさんの原書を理解するのが難しかった自分にとって、これらを扱った章は嬉しかったです。


特に、状態をEnumで表現し、状態遷移を遷移前の状態と遷移後の状態のSetの2つの組み合わせで管理するという考えは、積極的に使おうと思いました。 このテクニックは、特定の定数の列挙を表現するEnumの特性を適切な箇所で活かしているプログラミングです。 誤った状態に遷移することを防ぐだけでなく、遷移を管理するクラスを見るだけで何から何に遷移するのかの仕様が一目で見れるのは秀逸です。


2. クラス図の考え方

クラス図はどのクラスがどんな責務を持っているかを書く図です。属性やn対mなどのクラス間の関係を書くのはER図。 歴が浅い人はクラス図に見せかけたER図書きがちと読書会でとあるベテランさんが怒ってらっしゃったのを思い出しました。

テーブル構造をまんま反映したクラスしか使わないFWしか出来なかったら頃にそれをやったので、改めて胸に刻みした笑  どんな風にクラス図を作成するのか、書かれていました。

クラス図はロジックに着目しています。誰が何をする業務の流れになっているのか。業務にはどんなものが必要なのか。


例えば、図書館の貸し出し管理システムなら、利用者が本を借りなければ返却することはできません。 ですから、返却の前に必ず貸し出しがあります。

貸し出しは本がないと出来ないので貸し出し処理と本はセットで… 返却には返却日が必要なので貸し出し時に計算して… というようにどんな処理をする必要があるのか、どんなデータが必要なのか、データはどういうロジックで導き出されるのかを考えます。


業務の関心ごとはヒト・モノ・コトで整理するそうです。特にコトを中心に自然言語でまず業務を整理します。コトを整理すると、ヒトやモノも見えてきて、データやロジックに落としこみやすいです。コトを中心に広げていくのが良いなと思いました。ちゃんと整理した状態で作るのでプログラムにも落としこみやすいし、こういう風に作られたクラス図をベースにすると、流れが分かり易いプログラムが書けそうです。

この本と一緒に読むと面白そうな本

1. ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

https://www.amazon.co.jp/dp/B082WXZVPC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

「現場で役立つシステム設計の原則」では、戦術的DDDについては値オブジェクト・ドメインサービスを重点的に扱っていますが 「ドメイン駆動設計入門」ではより多くの戦術的DDDの手法を扱っています。 こちらも具体的なコード付きで解説しています。 「現場で役立つシステム設計の原則」で扱っている以外の戦術的DDDの手法を知りたい人におすすめです。

2. オブジェクト指向でなぜつくるのか 第2版

https://www.amazon.co.jp/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%81%A7%E3%81%AA%E3%81%9C%E3%81%A4%E3%81%8F%E3%82%8B%E3%81%AE%E3%81%8B-%E7%AC%AC2%E7%89%88-%E5%B9%B3%E6%BE%A4-%E7%AB%A0/dp/4822284654

オブジェクト指向が誕生するまでの歴史を機械語からたどっています。

機械語アセンブリ言語手続き型言語オブジェクト指向… それぞれどんな背景で誕生し、どんな課題が生まれて新しいプログラミング手法の誕生につながったのか。プログラミング手法の特性を歴史という観点から知ることで、また考えが深まった気がします。

この本は実務に直接役には立ちません。しかし、オブジェクト指向誕生以前のプログラミング手法の問題点とオブジェクト指向がどう解決したのかを知ることで、オブジェクト指向を生み出し改善してきた人たちにリスペクトして特性を活かして書こうというモチベーションになる気がします。

【Python】【TypeHint】ImmutableなオブジェクトにProtocolを使った時にmypyがエラーとなった

普段mypyを使って開発をしているのですが、Immutableなオブジェクトにtyping.Protocolを使ったらmypyがエラーを出して困ったので備忘録として記事を書きます。

Protocolとは

Wikipediaには「複数の者が対象となる事項を確実に実行するための手順について定めたもの」と書かれいています。

情報工学の文脈については、「情報工学分野でマシンやソフトウェア同士のやりとりに関する取り決め(通信規約)を指すためにも用いられるようになった」と書かれています。

https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB

異なるもの同士がやりとりをするための取り決めが「プロトコル」と言えそうです。

では、PyhtonのProtocolは何でしょう。

PythonのProtocolはPEP544で定義されました。PEP544を読んでみると

Currently, PEP 484 and the typing module [typing] define abstract base classes for several common Python protocols such as Iterable and Sized. 

collections.abc --- コレクションの抽象基底クラス — Python 3.8.2 ドキュメント

(みずき訳)
現在、PEP484とタイピングモジュールではIterableやSizedなどのPythonのプロトコルの抽象基底クラスが定義されています。

IterableやSizedはプロトコルだそうです。

Iterableだと、__iter__メソッドを持っているのが規約、Sizedは__len__メソッドを持っているのが規約のプロトコルです。

これらの規約を満たしているオブジェクトに対してiter()やlen()が使用できます。

逆に、満たしていないオブジェクトには使用できないので、__iter____len__を実装して規約を満たす必要があります。

PythonはIterableやSizedなどのプロトコルを用意してくれていますが、それらでは足りない独自の規約を作りたい時があります。

そんな時に、typing.Protocolを継承したクラスを作って独自の規約を作ります。

PEP 544 -- Protocols: Structural subtyping (static duck typing) | Python.org

typing.Protocolの使用例

https://www.python.org/dev/peps/pep-0544/ を少し改変しました。

from typing import Protocol, Iterator


class Template(Protocol):
    name: str        # プロトコルの要素
    value: int = 0   # プロトコルの要素(デフォルト値つき)

    def method(self) -> None:  # プロトコルの要素
        pass


class Concrete:
    def __init__(self, name: str, value: int, used: bool) -> None:
        self.name = name
        self.value = value
        # この要素があってもname, value, method()があるので
        # Templateクラスのプロトコルを満たしていると言える
        self.used = used

    def method(self) -> None:
        return

# ConcreteクラスのオブジェクトをTemplateクラスと型推論しても
# プロトコルに則っているのでエラーにならない
sample: Template = Concrete(name='aaaa', value=1, used=True)


# Iteratorのプロトコルに則っていないのでエラーになる
# Incompatible types in assignment (expression has type "Concrete", variable has type "Iterator[int]")
sample: Iterator[int] = Concrete(name='aaaa', value=1, used=True)

エラーになった例

ImmutableなオブジェクトにProtocolを使用しようとした時にmypyがエラーを出しました。

import dataclasses
import datetime
from typing import (
    Iterable,
    List,
    Protocol,
)


class Item(Protocol):
    name: str
    release_date: datetime.date


@dataclasses.dataclass(frozen=True)
class Book:
    name: str
    release_date: datetime.date
    author: str


@dataclasses.dataclass(frozen=True)
class Movie:
    name: str
    release_date: datetime.date
    director: str
    casts: List[str]


def aggregate_items(items: Iterable[Item]):
    pass


book_1 = Book(name='本1', release_date=datetime.date(2020, 1, 1), author='著者名')
book_2 = Book(name='本2', release_date=datetime.date(2019, 1, 1), author='著者名')
movie_1 = Movie(name='映画1', release_date=datetime.date(2010, 2, 1), director='監督名', casts=['女優', '俳優'])

items = aggregate_items([book_1, book_1, book_2, movie_1])

エラー内容

error_example.py:38: error: List item 0 has incompatible type "Book"; expected "Item"
error_example.py:38: note: Protocol member Item.name expected settable variable, got read-only attribute
error_example.py:38: note: Protocol member Item.release_date expected settable variable, got read-only attribute
error_example.py:38: error: List item 1 has incompatible type "Book"; expected "Item"
error_example.py:38: error: List item 2 has incompatible type "Book"; expected "Item"
error_example.py:38: error: List item 3 has incompatible type "Movie"; expected "Item"

なお、frozen=Trueを外したり、他のMutableな型で試した結果、エラーにはならなくりました。 このことから、ImmutableなオブジェクトにProtocolクラスを参照させようとするとエラーになるとわかりました。

解決策

Protocolを継承したクラスのアトリビュートに対して@propertyを使用することで、Immutableなオブジェクトがアトリビュートになった場合でもエラーにならなくなります。

propertyは、例えばアトリビュートnameの場合はget_nameのようなものです。nameという名前のアトリビュートの値が返されます。

https://github.com/python/typing/issues/622

class Item(Protocol):
    @property
    def name(self) -> str:
        pass

    @property
    def release_date(self) -> datetime.date:
        pass

なぜ、name: strではいけなくてpropertyでないといけないのでしょう。調べていたところ、この記事に出会いました。

pythonのtyping_extensions.Protocolがなぜ嬉しいか(propertyの例) - podhmo's diary

先程のエラーにerror_example.py:38: note: Protocol member Item.name expected settable variable, got read-only attributeというメッセージがありました。

Protocolクラスの属性はwriteableな変数を期待しているが、read-onlyな属性渡されていると言っています。

name: strだとread-onlyなアトリビュートとみなされます。

そのため、@propertyにして書き込み可能なアトリビュートに宣言してあげます。

ただ、Protocolクラスではwritableとなっているだけで、BookクラスやMovieクラスで属性の値を再代入したらエラーとなるので、安全です。