オブジェクト指向言語で、自分のインスタンス変数に値をセット/更新するメソッドを許せるか

<?php
class SearchClient
{
    public function execute()
    {
        setupParameters();
        $this->resultSet = /* どこから出てきたのかわからない値を使って処理をする */
    }

    private function setupParameters()
    {
        $this->params = /* なんかやばい処理 */;
    }
}

みたいなコードを許せるか?という話である。 例示したコードは前処理的な役目が強調されているが、それに限らずいろんなメソッドからいろんなプロパティがゴリゴリ更新されるパターン全般を指す。

最初は「必要なときもあるはずだからそのユースケースを考えよう」という記事にする予定だったが、書いてるうちに許せない気持ちになってきたのでそういう記事にする。 以下では、自分で思いついたこういう処理が必要なユースケースを述べ、さらにそれに対する反論を述べる形式で進める。

初期化時点では必要ないけど特定の処理を開始する時には必要な処理がある

それは責務が複数混じったタタリ神なので、これまで君臨してくれたことに感謝をしつつタタリの元を切り離そう。

初期化すべきプロパティがたくさんあるから仕方ない

そんなに抱え込んで、タタリ神でないといえるのか? いえるしても任意のタイミングで呼べてしまう関数で初期化するのはタタリ神の元だから全部コンストラクタでやって、その後プロパティは変更しない方が良い。

そうはいっても状態を管理しないといけないんだから仕方ない

うーん。 毎回新しいインスタンス作れよ、と漠然とは思うけど、それでワークする具体的なケースがイメージできない...。 他のプロダクトはどうしてるんだろう。 状態管理のプラクティスとかあるのかな。

あったわ。

nekogata.hatenablog.com

なるほど、デザパタね。不勉強がバレたけど、子要素のインスタンスを毎回生成して、そいつは決められたルールでOutputを返すから状態管理が楽になるよ、ってことか。

『現場で役立つシステム設計の原則』の画面とドメインオブジェクトを一致させる章を読んだが異文化に衝撃を受けた

f:id:blackawa:20171010152910j:plain

異文化すぎて理解に苦しむ箇所もいくつかあったが、もちろん面白い箇所もあった。

タスクベースの画面いいよね

たしかに。しかも後の方で「とはいえなんでもできる画面の需要が尽きないのもわかるから、そういう時はドメインオブジェクトは分けつつもそれらを協調させるドメインオブジェクトを一つ切るといいよ」と書いてあって、さらに納得感が増した。 使えるようになるために学習が必要だけど使えるようになると1画面でなんでもできる、ってのはたしかに(特に社内ツールとかで)尽きない需要だと思う。

どこまでは「論理ビュー」で、どこからが「物理ビュー」なのか?

ここが自分ではよくわからなかった。読書会を一緒にやった同僚たちも首をかしげていた。 論理ビューとはプログラミング言語で出力を担保するビュー情報のこと、物理ビューとはテンプレートエンジンで出力を担保するビュー情報のことのようだ。

筆者いわく

  • 段落の論理構造は String[] と表現できる論理ビュー
  • <p>タグとか、段落の先頭を全角一文字で字下げするといった物理的な手段」は物理ビュー
  • 場合によって出し分ける文言は論理ビュー(0件なら「見つかりませんでした」、1件なら「1件みつかりました」など)
  • HTMLのclass属性は論理ビュー

あ、class属性はアプリケーションコードで持って良いんだ、と驚いた。 それまでの3つはたとえばHTMLでもJSONでも必要な情報だし、むしろどちらでも使える情報だからビューよりのドメインオブジェクトに持っておくのは筋が良いと思う。 そもそもビュー専用のオブジェクトが存在するのはよくあるパターンだし、全然理解できる。

でもclassはHTMLでしか必要ない。 その後で「画面を表示するロジックにif文が入り込み始めたら、要注意です」とあるが、たとえばclass属性の出し分け条件をドメインオブジェクトに寄せたとして、特定の条件の時にDOM自体を出したり出さなかったりする時はどうするんだろう。空の内容を描画しちゃって display: none; はあまり筋が良いと思えない。

自分でこの本の内容を実践する時、どうするかなぁ。 ビューから全くif文がないこと、はコード品質の合格条件には入れられない気がする。

Clojureで書けば全部Clojureだからビューだろうがなんだろうが簡単に単体テストを書けるんだけど、それとこれとはちょっと話のレイヤーが違うからそれで解決したことにしちゃうのは反則技っぽいし、もうちょっとぼんやり考えよう。

その後

上の説明の後は、コードも段落分けすることだとか、プロパティをグルーピングすることなどが載っていた。そういう意味でのキレイさの話。

今から購入を伴うメディアサイトを作るとしたら、どんなアーキテクチャを採用するか

> **NOTE** 思考実験なので想像に過ぎないが、より良いアイデアをお持ちの方は、ぜひご教授ください。

 

想像してみる。

 

今はだれもがスマホを持っていて、SNSを読み、バズった記事にアクセスが殺到する。

死にゆくサービスも多くある一方で、生き残ったサービスはその場しのぎに書いた負債の利子の返済に苦しめられる。

RDBのスケールアップの限界と戦う。

 

これからもスマホをはじめとしたデバイスからの通信量は増え続けるだろう。

そんな今の時代、購入を伴うメディアサイトを立てるとしたら、どんな構成を採用すべきか?

 

> 購入を伴うとは、ここでは求人への応募や賃貸の内見応募、ECでの商品購入、定期購読料の契約、飲食店の予約などの、サイトの収益の源となる行動のこととする。

> つまりいわゆるニュースサイトのような広告収益モデルではないメディアサイトの話。

 

## やりたきこと

スパイクアクセスに耐えられること。

言語の流行り廃りに耐えられること。

開発速度を保ち続けられること。

 

## Wordpressはどうだろう?

この記事を書きはじめてすぐは、自前で全部書くならどんな構成にするか?としか考えられていなかったが、そういえばWordpressというものがあったなと思いついたので考えてみる。

ここではWordpressを取り扱うが、実際にはメディアサイトの雛形となるいろいろなパッケージ全般が考慮対象だろう。

 

### スパイクアクセスに耐えられるか?

ググったらあっさり出てきた。

https://engineer.blog.lancers.jp/2017/03/awswordpressのスケールアウト/

AWS上で動かすためにいろいろ引っぺがしたら良いらしい。なるほどー。

 

http://xn--o9j0bk9rwb2ftig7dzeb2881pquxd.com/rentalserver/post-512/

こんなのもあるらしい。これだけベンダー依存するのはちょっと心配な気はする。

 

### 言語の流行り廃りに耐えられるか?

PHPくらい枯れた言語とWordpressみたいな古株パッケージなら、バージョンアップをサボらなければしばらくはなんとかなりそうに思える。

機能が増え過ぎて、もうこれWordpressっていうより半分以上自前のPHPサイトだよ、ってなった時、それでもバージョンアップに耐えられるコードを書く必要はありそうだが、それはどの言語でも必要なコストだ。

あるとすれば、Wordpressとそれ以外のコードを疎結合にして別々にバージョンアップできるようにしておいた方がよい気がする。

たとえばコンバージョンにからむ部分はWordpressとは別のサブドメインやURLに切り出しておけば良さそう。

あるいはログイン機構は別に持って、他のサービスが認証基盤にアクセスしに行く、とか。

 

### 開発速度を保ち続けられるか?

正直まだPHPを書いた経験が少ないのでなんとも言えない。

…が、PHPはテンプレートエンジンとして作られたのだし、今は型システムやパッケージマネージャーもあって立派な言語になったけど、あんまり巨大なシステムを書くのにマッチする言語ではないような気はする。

が、単なる経験不足と好みを孕んでる確信があるので、何で書くかはあまり大事じゃないんだろう。

探せばJavaRubyのメディアサイトのパッケージだってあるだろうし。

 

## 自前

こっちは最初に、自分ならどうするだろうかと考えた方の選択肢だ。

 

とはいえ上で考えたことでだいたい話は終わってしまう。

 

メディア部分も自分で書いて、言語が廃れても逃げ出せるように小さいサブシステムに保つ。

必要ならRDB以外も検討する。

 

### 管理画面をどうするか

ここまで考えて思いついたのだが、Wordpressにはセットで付いていた管理画面はどうしようか。

別に我々は必ずセットにする必要などないので、別システムに持っておいても問題ないが…

 

ここでDBを共有してしまってはサブシステムを小さく保ちたいのに変に結合してしまう。

ここは、メディアサイト本体にAPIを用意して、管理者の認証認可とAPIをキックするだけの管理サイトを用意するのはどうだろう。

それなら、外に公開するサイトには特定のURLを404で返すように設定しておいて、内部向けに用意した1つのインスタンスだけはすべてのURLを公開するようにしてサブシステム間の結合を疎に保てそうだ。

こうしておけばDBを1つのサブシステムで占有できるし、サブシステムを小さく保てるから保守も書き換えもやりやすそうだ。

 

関心ごとが違うサブシステムを適切に疎結合にするならば、ログインが必要ならそのまわりも外に出せるし、お金を扱うのも外に出せる。

 

ただし各サービスを別々に互いのエラーに寛容に作らねばならないのは、開発スピードをある程度落としそうだ。

未来に抱え込む負債を減らせるなら、そのコストは充分ペイすると思うが…どうなんだろう。

 

結局は想像だ。

レガシーコードをなんとかして読み解く方法

レガシーコードをリプレイスするタスクで、レガシーコードが全然読めなくて時間を浪費しきってもまだ何もわからなくて詰んだ。 先輩エンジニアに相談したら即席コードリーディング会を開く声掛けをしてもらえて、どうレガシーコードを追うのか見ることができた。

今日はそのときの学びを書いておく。

タスク内容

n年モノのメディア系PHPコードのごく一部のビュー(リンク集的なパーツ)の描画ロジックを別システムにリプレイスする。

学び

全部を追わない

何より全部を追うとツラすぎるし、得られる完璧な仕様もコストに見合わない。

外部仕様をざっくり理解する

今回はHTMLがアウトプットになるので、どんなHTMLがどのコードから出てくるのか、その末端から理解すると良いということがわかった。 リンクに必要なURLやタイトルがどういうオブジェクトから生成されているのかをまず把握する。 そして次にそのオブジェクトがどの別のオブジェクトから生成されているのかを把握していく。でも深入りはしない。

行間を読む

正直、今となっては命名が微妙だったり不要な引数を受け取っていたり、必ずある値しか来ないのにif文が書かれていたりすることは全然ありえる。 そうなった時に今までの歴史を知っている人に聞くことはもちろん大事だけど、それとは別に「当時やりたかったことを察してあげる」ことも大事だと学んだ。

今回はPHPだがあの名著「SQLアンチパターン」にも、

問題の解決を意図しながらも、しばしば他の問題を生じさせてしまうような技法

と書かれている。たとえあるコードがアンチパターンを踏んでいたとしても、特定の状況を解決したといえるはず。 それをよりスマートな方法で実現して、今お金を稼いでくれているコードへの弔いにしよう。

関数の引数を追わない

これをやり始めると無限に違う箇所を読み続けて、自分が何者だったのかも忘れてしまう。 僕は今回これを何度もやらかして時間も精神力も浪費した。

先輩エンジニア氏は、外部仕様をざっくり把握するにあたって関数の引数が現れたら、その時に呼ばれている関数だけを調べてなんとなくそのオブジェクトの役割を理解すると言っていた。 たしかに、一応オブジェクト指向で書かれているんだからあるオブジェクトのすべての振る舞いではなくて必要な振る舞いだけを調べればいいはずだよな。 それに、リプレイス対象の関数が(当時必要だったかもしれないけど)今や不必要な大きなオブジェクト(HttpRequestみたいなやつ)を引数に受け取ってしまっている可能性は全然想像できる。そうだった時に、引数を先に追うと必要な処理に必要ない情報ばかり掴まされることになる。

ざっくり分かったらとりあえず書き始める

なんとなくどんな条件の時にどんなビューが返ればいいのか分かってきたら、もう書き始める。 これでちょっとずつキレイなコードを吐いて、脳内に覚えておかなければいけない仕様を減らす。で、またオカワリする。

道は遠いが、僕はまだレガシーコードと出会ってそう時間が経っていない。 単にレガシーコードを書くスキルレベルが1なのだ。

ありがたみと滅せよという気持ちを内在させながら、その念を飼い慣らしていこう。

読書会のスタイル別・メリット/デメリットを考える

自分でも読書会を開催する日が遠からず来た時に、自分ならどんなスタイルで運営するだろうか?と考えあぐねた。 今まで参加した読書会にはいくつかスタイルがあり、それぞれにメリデメがあると想像できたので、考えてみることにする。

今までに参加した読書会のスタイル

プレゼン式

章ごとに担当者を割り当てて、担当者がその章の内容とプラスアルファの内容をプレゼン形式で発表する。 また、会によってはディスカッションの議題が提示されてグループワークを行うこともある。

音読式

1, 2ページくらいの文章を指定して、その場で決まった担当者が音読する。その後、気になったことを議論する。これを繰り返す。

黙読式

1, 2ページくらいの文章を指定して黙読する。その後、その部分の論理構造や内容について議論する。これを繰り返す。

メリデメ

表にしてみた。

評価点 プレゼン式 音読式 黙読式
時間あたりの進みが速い ⭐⭐⭐ ⭐⭐
1回休んだあとの参加しやすさ ⭐⭐⭐ ⭐⭐
メンバーの負担が低い ⭐⭐ ⭐⭐
議論の粒度が細かい ⭐⭐ ⭐⭐
大人数でやりやすい ⭐⭐⭐ ⭐⭐

総合的にどの場面でも適した読書会の形式はなくて、これらのパラメータのうちどれを重視するかだと思う。

プレゼン式を採用すると良さそうなケース

  • 部屋を取りにくいなど、回数を重ねること自体が負担になる場合。
  • 読むスピードの足並みを揃えるのが難しい大人数の場合。

音読式を採用すると良さそうなケース

  • ライト層に「とりあえず出てみるか」と思ってもらいたい場合。
  • メンバーが集まるコストが低い場合。
  • まとまった時間が取れない場合。

黙読式を採用すると良さそうなケース

  • 少人数で少しずつ議論をして認識をあわせながら進みたい時。
  • メンバーが集まるコストが低い場合。(音読式と同じ)

現場で役立つシステム設計の原則のデータソース層とドメインモデルのつなぎこみに感動した

f:id:blackawa:20171010152910j:plain

www.amazon.co.jp

会社の同僚と読み合わせをしているのだけど、面白い本。 Java(というかSpringBoot)を使って、ドメイン駆動設計の考えに従って変更に強いアプリケーションを設計・実装していく方法がまとめられている。

レイヤードアーキテクチャドメインモデル

増田さんは以下のような3層 + ドメインモデルの構造を示している。

(わかりやすい絵)

が、読んでいると実際にはPresentation層、Presntation層と結びついたApplication層、Application内部の小さな業務ロジックを担当するApplication層、ドメインモデル、ドメインモデルの一部としてのDatasource層、実際にSQLAPIで情報を取得したり登録したりするDatasource層、くらいには層が分かれているねという理解になった。

(わかりやすい絵)

こういう暗黙の了解とか言い換えはそこそこ本著に入ってる気がした。

Datasource層とDBのコミュニケーションどうするの?

レイヤードアーキテクチャの章で感動したのは、データソース層とDBのコミュニケーション方法。 本著では、アプリケーションのシステム的構成と業務ロジックを「レイヤードアーキテクチャ」と「ドメインモデル」に分離しようとしているが、読み進めていて「いやそれはわかるけど、結局データソースとつながないといけないじゃん」と思っていた。

そしたらサンプルコードと共に解説されていた。

DataSourceのInterfaceはドメインとして定義して、それを実装する @Repository アノテーションがついたDatasource層を用意する。 Datasource層は内部でORマッパーを持っていて、そいつがDBとコミュニケーションして、結果をドメインモデルに詰めて返してくれる。

たしかにこれなら、Datasource層にアクセスしたらあたかもドメインが取得できているように振る舞える。

言いたいことはわかるけどどうやるの?と、言葉にできないレベルでモヤモヤしていたことの実装例を見られてスッキリした。

ゴツいCSVファイルをなんとかしてINSERT文に書き換える

Spacemacsに取り込めない50万行くらいあるCSVを、SQLに書き換えてDBに適用する必要があった。

こういう時、毎回コマンドラインを調べてる気がするのでまとめる。

改行コードCRLF -> LF

元データがExcelのシートで来たので。

cat data.csv | sed -e 's/\r\n/\n/g'

重複行削除

データに重複が混じっていたので。

sort data.csv > sort.csv
# 件数が変わっていないことを確認
wc -l *.csv

uniq sort.csv > data.csv

1行目にinsert文を書く

echo 'insert into xxx (...) values' > insert.sql

各行をSQLのパーツに使えるようにフォーマットして2行目以降に配置する

cat unique.csv | sed -e 's/^/(/g' | sed -e 's/$/),/g' >> insert.sql

最終行だけ ,; にする

sed -e '$s/,$/;/g' insert.sql > complete.sql

これで source すればok。 これ絶対また忘れるわ。