isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

クリーンアーキテクチャを俺なりに解釈する

クリーンアーキテクチャの意図の話をする。

具体的な実装の話はしない。

 

変数の純度

まず、変数の純度という概念を提唱したい。

ある値がどれだけ整理されているかの指標である。

エントロピー、ないし抽象度ということも可能だと思う。

 

Stringの"1,800"よりintの1800のほうが純度が高い。

JSTUTCならUTCのほうが純度が高い。

誕生日・現在時刻・年齢や定価・税率・税込価格のように3値の内2値が定まれば第3値が導出できるものは、3つ目を省いた方が純度が高い。

不正値が混じっているかもしれないデータと不正値が混じっていないことを確認したデータなら確認したデータの方が純度が高い。

おおよそRDBの正規化と同じ考え方だ。

 

純度の高低の見極め方

より汎用的に使いまわせるほうが純度が高い。

A→Bに変換する手間とB→Aに変換する手間を比較して、簡単な方が純度を下げる操作。

より不整合が起こりにくいデータ構造のほうが純度が高い。

よりその言語の文法や型システムを活かしたコードになる方が純度が高い。

純度を上げれば上げるほど、その値の表現の仕方は1通りに収斂する。

クリーンアーキテクチャの皮とは

純度の上下である。内側に行くほど純度が上がる。外側に行くほど純度が下がる。

 

この純度の山で各機能を表現するのがクリーンアーキテクチャだ。

実は数十行のコードでも、純度の上下を意識すると綺麗なコードがかける。

そこでclass分けやパッケージ分けにこの考えを適用しようという意図だ。

 

コードの読みやすさ、編集しやすさ

ここで読みやすく変えやすいコードの特徴を考えたい。

これは数十行のコードの読みやすさの話ではない。

その段階ではケツカンマの有無やタブ・スペースなどが議題に上がる。

そうではなく、数十万行のコードの読みやすさのことだ。

アーキテクチャ全体で保守性を高める特徴である。

 

第一に、処理が一方向に進む必要がある。良いコードはパッケージの分け方と処理の流れが対応する。どのサブパッケージの関数がどのサブパッケージの関数を呼び出すか明快に図示できる。それは一方向の矢印になる。大きな円になる場合もある。

 

第二に、同じことをするコードが複数あってはいけない。これはよく言われることだ。残念なことにプログラマーの心構えの問題とされがちである。そうではなく、これはアーキテクチャの問題なのだ。前の処理で潰しておくべき問題を解決せずに次の処理に入るから似たような関数を乱立させる羽目になる。

 

第三に、何かを判定するコードとその判定をもとに作業をするコードは厳格に別れなければいけない。サーバーサイドにおける作業とはHTTPレスポンス、DBに書き込み、キャッシュの破棄や作成、Push通知などだ。フロントエンドにおける作業とはファイルの書き込み、ダイアログの表示、画面の切り替えなどだ。厳格に分けれるほど使い回しが効くし、わかりやすいログが出せるし、修正しやすくなる。時々複雑なFor文の中の複雑なIf文の中で複雑なDB書き込みをしている人がいる。そういうコードはわかりづらいし、間違いに気づいたときにブレークポイントを貼ってもどの変数を見ればいいのかわからない。

 

第四に、入力が基準を満たしているか判定する処理が散らばっていてはいけない。本来それは処理の冒頭で行われるべきだ。そして、後続の処理は正当な入力がなされた前提で書かれるべきだ。

 

第五に、Util関数がたくさんあってはいけない。Util関数がなくなることはないと思うが、正しく責務が分割できないためUtil関数が増えすぎるのは設計が間違っているのだろう。

 

クリーンアーキテクチャとは

この五つの特徴を純度の観点で考えると単純な構造が見えてくる。純度が一つの大きな山の形になればいいのだ。処理の冒頭で入力値の純度を上げる変換をする。純度が上がり切ったら純度を下げる変換をする。

どうすればそれを実現できるかを考えると、クリーンアーキテクチャが導き出せる。

 クリーンアーキテクチャはこう書く

まず、最も純度の高い値の型を書く。これがクリーンアーキテクチャのEntitiesだ。ショッピングカートシステムならUser、Shop、Itemなどの型が該当する。

 

次に、Entitiesをインスタンス化するコードを書く。Entitiesの境界の外側からやってきたデータはすべてこの部分を通りEntitiesに変換される。例えばサーバーサイドなら

  • HTTP Request
  • Database
  • CSVファイル

などが該当する。フロントエンドなら

  • Http Response
  • Indexeddb
  • Mouse Event

などだ。この部分には

  • parseInt
  • json.Unmarshal
  • throw文

連発されるだろう。Optional型を持っていない資源からの入力の場合、ここでOptional型に変換するといい。注意してほしいのは、この段階で一切の判断や具体的な処理をしてはいけない。ただ単に純度を上げるためのコードだ。

 

次に、Entitiesと任意の引数から別の表現形式に変換するコードを書く。これがクリーンアーキテクチャのUseCaseに該当する。任意の引数とはその機能が実行されるトリガになったイベントの付加情報のことだ。ユーザ情報取得APIであれば、URLのクエリパラメータに指定されたUserIDなどが該当する。別の表現形式とは、サーバーサイドなら

  • HTTP ResponseのBody
  • DB操作コマンド
  • Push通知のコマンド

などだ。フロントエンドなら

  • ViewModel
  • ローカルファイル書き込みコマンド
  • HTTP Requestのパラメータ

などだ。ここでUseCaseを3層に分ける。

  • Entitiesの関数たち
  • 表現形式の型
  • 各機能の仕様だ。

Entitiesの関数たちとは、例えば

  • 商品の価格と税率から税込価格を計算し三桁ごとのカンマを入れる関数
  • ユーザーの誕生日から年齢を計算する関数
  • 購買履歴から通期の売上高を計算する関数
  • ユーザー一覧から20歳以上のユーザーのみを抽出したユーザー一覧を返す関数
  • AさんがBさんをフォローしているか返す関数

などだ。

特定の処理に使うことを想定しておらず、副作用がない。Entitiesのインスタンスメソッドとしてもたせるといいだろう。Entityのリストはリストを継承した専用クラスを作っておくとそこにメソッドをもたせれて良い。

表現形式の型とは

  • 画面に何を表示するか
  • 何をHTTPで返すか
  • DBに何を書き込むか
  • だれかにPush通知を送るのか

などを型で表現したものだ。

各機能の仕様とは、ビジネスロジックそのものだ。システムの各機能に相当する。サーバーサイドならAPI一個一個のことだ。これはEntitiesを引数に取りEntitiesの関数たちを活用しながら表現形式の型に変換する関数だ。

 

次に、コマンドをもとに外部APIを操作する処理を書く。ここがInterfaceAdapter層だ。どうせこの層はろくにテストできないので、極力簡単なプログラムになるようにコマンドの型を見直そう。

 

最後に各層をつなぎ合わせるコードを書く。

 

これがクリーンアーキテクチャだ。

様々な入力を最も純度が高い形に変換してから純度を下げるようにビジネスロジックを書いていくことがわかると思う。

 

注意点

いくつか絶対に守ってほしいことがある。

山頂を通過しないコードを書かない。

文字の”1,800”に消費税をかけて”1,944”を出力する関数などを書いてはいけない。

そのほうが全体のコード量が減る。通りの数問題の話だ。不必要な密結合も避けられる。Entitiesをハブにして疎結合にするべきだ。

 

ビジネスロジックの戻り値をもとに別の判断をするコードを書いてはいけない

一旦Entitiesから別の表現形式に変換したら、それをもとに別の処理をしては行けない。それらは劣化した情報であって、それに依存したコードを書くと保守性が落ちる。そもそも同じ処理ならEntitiesを引数に取った関数を書くほうが綺麗にかけるはずだ。また、クリーンアーキテクチャ中心部に集約したいコードが散逸するという問題もある。例えば、「このユーザー一覧をHTTP Responseでブラウザに返せ」というUserListResponseコマンドがあったとしよう。ある日、一度に返されるユーザーの数が多すぎるから登録日時降順にソートし上位20件を返すように変更することにした。一見、既存のUseCase層が返すUserListResponseコマンドの中をソートして配列をスライスすれば事足りるかもしれない。しかし、

  • ユーザー数を登録日順にソートする
  • Sliceする

というコードがビジネスロジックの外側に漏れ出している。その関数はUserEntitiesを引数にするように(あるいはUserEntitiesのインスタンスメソッドに)定義されるべきであり、ビジネスロジックはその関数を使うべきである。

 

以上がクリーンアーキテクチャの考え方だと思う。

おさらいしよう。

まず、もっとも純度が高い値の型を定義する。

入力値をその型に変換する。

その型を引数に具体的な処理を書く。



Alt SQLを作ることにした。

取りかかれるのはだいぶ先だと思うけど。



現行のSQLは重大な問題をいくつも抱えている。

だから、新しい文法のSQLが必要だ。

ちょうどDartが新しい文法でJSの世界を救ったように、SQLにも救世主が必要だ。



今の所SQLRDBを操作する唯一の言語だ。

RDBの背景には集合論を土台にした素晴らしいセマンティクスがある。

しかし、SQLの文法が悪いせいでRDBが扱いにくいものだと思われている。

よくあるイメージとして、これは間違った考えだが、KVSはRDBよりかんたんに扱えると思っている人がいる。

この状況を放置すれば人類は安直にKVSに流されてしまう。

 

また、RDBは速度に限界があると言われている。

いくつか話題になりやすい理由を上げる

  1. SQLのパースが遅い
  2. JOINが遅い
  3. ロックが遅い
  4. 挿入時の整合性確認が遅い

1はバイナリフォーマットを定義するとことで回避するべきだ。

2〜4はシステムの安全性とトレードオフだ。

RDBが正規化・トランザクション・整合性確認(参照整合性+チェック制約)という機能を持っているから安全なアプリケーションが作れる。

しかし、速度のためこの機能をあえて使わない選択がされてしまうことがある。

もっと速度を出しやすい文法にするべきだ。

 

速度を出しやすい文法とはどういうことだろうか。

RDBの低レベルのAPIを露出させてプログラマが最適化すれば速くなる。

しかし、それはやめたほうがいい。

逆に、プログラマはより抽象度の高いクエリを書くべきだ。

RDBMSによりたくさんの意図を伝えることで、RDBMS側が自動で最適化できるようにするべきだ。

 

実現方法として、トランスパイルすると標準のSQLに変換できる文法を新しく設計する。

 

解決されるべき課題

TABLEに名前空間がない

RDBではグローバル名前空間に全てのテーブル名がぶら下がっている。

しかし、別のテーブルとJOINせずにSELECTすることはほぼないと言い切れるテーブルもある。

実際のシステムでは半数近くがこのような二級市民テーブルだろう。

僕はMySQL WorkbenchやIntelliJSQL編集機能といったSQL専用エディタで開発をする。

専用エディタは入力中にコード補完をしてくれる。

FROM句やJOINキーワードを書こうとすると候補を上げてくれるのだ。

ところが、大きなシステムでは候補が大量に出てきてあまり意味がないことがある。

「このTABLEとJOINする可能性があるのはこのTABLEに決まっているのではないか」「トップレベルのFROM句にこのTABLEが出てくることはないのではないか」と思うことがある。

そこで、大量のテーブルを分類して名前空間を分けることができるべきだ。

そしてprivate句とpublic句を定義できるようにしたい。

 

フィールドに名前空間がない

UserテーブルやProductテーブルに、ものすごくたくさん列が存在するシステムに覚えはないだろうか。

 

フィールドを分類して垂直分割をすることも考える。

しかし、それはパフォーマンスの問題につながる。

そもそも垂直分割はアンチパターンということになっていると思う。

もっと簡単に列をネストできるべきだ。

 

例えばGo言語で

 

type User struct {

fieldA int

fieldB int

fieldC int

fieldD int

...

}

 

 

と長くなってきてしまったら

 

type User struct{

 subTypeA SubTypeA

 subTypeB SubTypeB

}



type SubTypeA struct {

 fieldA int

 fieldB int

}

 

type SubTypeB struct {

 fieldC int

 fieldD int

}

 

 

 

のように分割する。 同じようなことができてほしい。

これをRDBで扱う時はのような意味に変換するか?

もちろん、「構造体を保存できるようになりました」という話ではない。

実際は並列にカラムが並んでいるが、その中で名前空間を表現できるシンタックスシュガー、という意味に解釈する。

各フィールドは木構造を直列に変換して表現する。

オブジェクトの階層の区切り文字「.」を別の文字に置き換える。

 

// 保存したい値

USER.BODY.HEIGHT = 170

USER.BODY.WEIGHT = 60

USER.MENTAL.COURAGE = 5

USER.MENTAL.CALMNESS = 10

 

// 新SQL

CREATE TABLE USER(

 BODY (

   HEIGHT INT,

   WEIGHT INT,

 ),

 MENTAL (

   COURAGE INT,

   CALMNESS INT,

 ),

);



// トランスパイルされたSQL

CREATE TABLE USER (

 BODY__HEIGHT INT,

 BODY__WEIGHT INT,

 MENTAL__COURAGE INT,

 MENTAL__CALMNESS INT,

);



// 取り出し

SELECT BODY.* FROM USERS;

 

 



親子関係を直接表現する方法がない

一般にTABLEの親子関係を作るにはCREATE TABLE文内でFOREIGN KEYを使い、クエリ時にJOINする。

これをもっと簡易に書きたい。

それができないせいで配列を保存できるようなRDBが出てきてしまうと思う。

 

親TABLEが一つの場合

一旦複数の親を持つTABLEのことは置いておこう。複雑だ。

自分が考えたのはこのような文法だ。

 

CREATE TABLE USERS (

 ID INT,

 NAME VARCHAR

);

 

CREATE TABLE USERS.FAVORITE_FOODS (USERS.ID AS USER_ID) (

 USER_ID,

 FOOD_NAME VARCHAR

);

 

 

まずCREATE TABLE USERS.FAVORITE_FOODSで、FAVORITE_FOODSはUSERSにぶら下がっているということを明示している。

さらに(USERS.ID AS USER_ID)でUSERS.IDはFAVORITE_FOODSのUSER_IDと同じだと宣言している。

これによりFOREIGN KEY文よりわかりやすく親子関係を表現している。

 

クエリはこのようになる。

カレーが好きな人一覧なら

SELECT * FROM USERS WHERE USERS.FAVORITE_FOODS IN "カレー";

のような感じになるだろう。

このあたりは、相関クエリのためにサブクエリを省略できる文法として設計するか、

集計関数のためにFROM・WHERE句を省略できる文法として設計するかちょっと悩んでいる。

 

親TABLEが2つある場合

親TABLEが2つある場合、上記の文法では対応できない。

この場合、そもそも2つのテーブルをJOINした結果のTABLEを定義できるようにし、

そのTABLEに上の文法で子供を作れるようにする。

ユーザー一覧のUSERSと食べ物一覧のFOODSがあり、各ユーザーの好きな食べ物をFAVORITE_FOODSに保存する。

 

CREATE JOINED TABLE USERS, FOODS AS USERS_FOODS;

 

CREATE TABLE USERS_FOODS.FAVORITE_FOODS (USERS_FOODS.USER.ID as USER_ID , USERS_FOODS.FOODS.ID as FOOD_ID) (

 USER_ID,

 FOOD_ID,

);

 

 

一見ただのCREATE VIEW文と変わらないが、あえてCREATE JOINED文を定義することに意味がある。

CREATE VIEW文はあらゆることに使えるのでなんのために作ったViewかわからない。

それに対してわざわざCREATE JOINED TABLE文を使っていたら、

この組み合わせに対して子TABLEをぶら下げるためにこの中間テーブルを作ったというメッセージが伝わりやすい。

 

ダイヤモンドになると最悪

親子関係がダイヤモンド型になる時に、外部キーを正確に書くのは非常に難しい。

 

ライトテーブルに夕食のメニューテーブルと乗客テーブルがぶら下がり、

その両方にぶら下がる乗客の食事メニューテーブルのことを考えよう。

 

CREATE TABLE PASSENGERS_FOODS (

 PASSENGER_ID int,

 FOOD_ID int,

 FOREIGN KEY PASSENGER_ID REFERENCES PASSENGERS (PASSENGER_ID),

 FOREIGN KEY FOODS_ID REFERENCES FOODS (FOODS_ID)

);

 

 

これではだめだ。

このようにうっかりするとフライトAに乗る乗客にフライトBのメニューを設定できるテーブルが出来上がる。

 

正確にはこうだ。

CREATE TABLE PASSENGERS_FOODS (

 FLIGHT_ID int,

 PASSENGER_ID int,

 FOOD_ID int,

 FOREIGN KEY (FLIGHT_ID, PASSENGER_ID) REFERENCES PASSENGERS (FLIGHT_ID, PASSENGER_ID),

 FOREIGN KEY (FLIGHT_ID, FOODS_ID) REFERENCES FOODS (FLIGHT_ID, FOODS_ID),

);

 

この場合CREATE JOIND TABLE文をカスタマイズすればいい。

 

CREATE JOINED TABLE PASSENGERS, FOODS AS PASSENGERS_FOODS

 REPRESENTATIVE KEY (PASSENGERS.FLIGHT_ID, FOODS.FLIGHT_ID) as FLIGHT_ID;

 

 

このようにPASSENGERSとFOODSのデカルト積からFLIGHT_IDが一致しない行を取り除いている。

おかげで、このJOINED TABLEに子テーブルをぶら下げれば、必ずFLIGHT_IDの掛け違いが起こらないようになった。

 

CREATE TABLE PASSENGERS_FOODS.PASSENGERS_FOODS

 (PASSENGERS_FOODS.PASSENGERS.PASSENGER_ID as PASSENGER_ID,

  PASSENGERS_FOODS.FOODS.FOODS_ID as FOOD_ID) (

 PASSENGER_ID,

 FOOD_ID,

);

 

 

似たようなテーブルを複数作るのがめんどくさい

よく、RDBには継承がないと言われる。

しかし、そのような大掛かりな機能を導入するのは難しい。

欲しいのは他のテーブルのCREATE TABLE文をコピペする構文だ。

JSのスプレッド演算子のようなものがあればいいと思う。

 

CREATE ABSTRACT TABLE PERSON (

 NAME VARCHAR,

 AGE INT,

);

 

CREATE TABLE CUSTOMER (

 ID int,

 ...PERSON,

 PHONE VARCHAR,

);

 

CREATE TABLE EMPLOYEE (

ID int,

...PERSON,

JOB VARCHAR

);

 

 

こんな感じだ。

 

3値論理が複雑

やはりOptional型は必要だと思う。

よく「NULLとFALSEを比較したらどうなるか」などのまとめ表がSQLの教科書に載っている。

ありがたいと思うが、そもそもNULLABLEとそれ以外の値を演算できるのがおかしい。

NULLABLEをunwrapせずに演算しているところをすべてコンパイルエラーにするべきだ。

JavaPHP等のRDBMSドライバもNULLABLEのintはintではなくOptional<Integer>で返すべきだ。

新しく追加されたcase文でこれまでに比べれば便利になったが、しかし依然として文字の量が多いので、

もっと便利な構文がほしい。

RDBにもOptional型を追加し、get(), orelse(), is_empty(), is_present()等の関数を追加するべきだ。

 

ユーザー定義型

また、ユーザー定義型も欲しい。

しかし、RDBにおいてスカラ値ではなくベクトル値を保存できてしまうのは良くない。

必要なのは、TABLEでは複数の列を使って値を保存するが、SQL上は一つの構造体として扱えるものだろう。

似たようなものを挙げるとC++だ。

C++のクラスはオブジェクトをメモリ上にどのように表現するかプログラマが責任を持って決めないといけない。

同じようにRDBも構造体を保存できる機能を追加するのではなく、SQLでは構造体として扱われる物をTABLE上でどのように表現するかプログラマが責任を持って決めるべきだ。

例えば何型の列が何個いるか、どんなチェック制約が必要かについて決める必要がある。

 

ついでに代数的データ型もほしい。

 

WITH句にクエリではなく関数を書きたい

自分はJavaScriptなどで高階関数を書くとき、その式が長くなりすぎるならローカル関数を定義し高階関数の中を簡略化することがある。

例えば

function filterUserByGender(users, gender) {

 

 function genderEqual(user) {

    // これならここの式がどれだけ長くなってもストレスがない。

    return user.gender == gender;

 }

 

 return users.filter( u => genderEqual(u));

}

 

SQLも同じように書きたい。

 

複雑な相関クエリをCREATE TEMPORARY TABLE文で簡単にするのはよくあるパターンだ。

クエリによっては大きな効果が得られる。

TwitterでWITH句を使うように教えていただきました)

しかし、相関の仕方が複雑なとき意味をなさないことがある。

これはある会社の各課で誰が一番若いかを集計するクエリだ。

 

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = (SELECT MIN(AGE) FROM EMPLOYEES as E2 WHERE DIVISION = E2.DIVISION);

 

 

これを一時テーブルで簡略化してみよう。

 

CREATE TEMPORARY TABLE DIVISION_MIN_AGE AS

(SELECT DIVISION, MIN(AGE) AS AGE FROM EMPLOYEES GROUP BY DIVISION);

 

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = (SELECT AGE FROM DIVISION_MIN_AGE WHERE DIVISION_MIN_AGE.DIVISION = EMPLOYEES.DIVISION);

 

 

このように全く意味がない。

なぜ意味がないのか。

このクエリで一番冗長な箇所はサブクエリのWHERE句だ。

「DIVISION_MIN_AGE.DIVISION = EMPLOYEES.DIVISION」

この部分を外に出す方法がないから意味がないのだ。

 

そこで、WITH句に関数を宣言できるようにしたい。

 

WITH FUNCTION IS_MIN_AGE(VARCHAR(10) DIVISION, INT AGE) AS (

 (SELECT MIN(AGE) FROM EMPLOYEES WHERE EMPLOYEE.DIVISION = DIVISION) == AGE

)

SELECT

ID , NAME

FROM

EMPLOYEES

WHERE AGE = IS_MIN_AGE(ID, AGE);

 

大変綺麗になったと思う。

 

木構造でResultが取りたい

これはSQLというよりドライバの問題かもしれない。

あるクエリの結果の行をもとに別のクエリを投げる、ということがある。

アプリケーション側が一回目のクエリの結果を使わないなら、WHERE INを使えば解決する。

しかし、アプリケーション側が一回目のクエリの結果を使うならば、少なくとも二回に分けてクエリを投げなければいけない。

下の表を見てほしい。

 

 

f:id:isyumi-net:20180810190449p:plain

 



この表を出力するにはアプリケーション側で下のような2重For文を書かなければいけない。

先に都道府県一覧を取得し、その結果をもとに市町村一覧を取得する。

疑似コードだがこんな感じであろう。

 

foreach($pref_row as $connection->fetchAll("SELECT * FROM PREFECTURES")) {

 foreach($city_row as $connection->fetchAll("SELECT * FROM CITIES WHERE PREFECTURE_ID = ${pref_row->id}"){

   print $pref_row->name, $city_row->name, $city_row->population;

 }

}

 

 

少し悔しい。

SELECT文が木構造に対応してくれたらいいのだ。

 

SELECT

PREFECTURE_ID ,

PREFECTURE_NAME,

VISITOR (SELECT * FROM CITIES WHERE PREFECTURES.PREFECTURE_ID = CITIES.PREFECTURE_ID) AS CITIES

FROM PREFECUTRES;



foreach($pref_row as $connection->fetchAll($sql)) {

 foreach($city_row as $pref_row->cities){

   print $pref_row->name, $city_row->name, $city_row->population;

 }

}

 

 

綺麗に書けた。

冒頭でAlt SQLSQLに変換して使うと書いたが、もしRDBMSがこの構文に対応してくれるならパフォーマンスが向上するであろう。

上のコードの場合RDBMSは1回目のクエリ結果をもとに二回目のクエリがやってくるということを知らない。

しかし下のコードであれば、HDDからRAMに読み込んだデータやカーソルを意図的に使いまわすことが可能になる。




コード書く工程は末端作業か?

お断り
  1. 今起きている何らかの喧嘩は関係ありません。未来に向けて書き残しておこうと思ったから書いています。
  2. 「そうやって現場を大事にしないから日本はだめになったんだ」的な話も、今は関係ありません。それはそれ、これはこれ。あくまでソフトウェア産業の話をします。

 

まず、僕の現状認識を説明する。これは自分が体験したことではない。また、統計など信用のおける証拠があるわけでもない。あくまで、伝聞で得たイメージである。

ソフトウェア産業では10年以上前からプログラミングが儲からない仕事になっていくといわれていた。商品開発とアフターサービスの付加価値が高まり、逆にプログラミングの付加価値が下がるであろう。コーディングはどんどん発展途上国に外注されていく。だから日本人は生き残るために別の道を選ぶべきだ。いまプログラマーとして働いている人は設計や管理職などのキャリアへ進むべきだ。というのが定説だった。

しかし、その変化は起こらなかった。プログラミングの価値はますます増大し、プログラマーは稼げる仕事になっていった。多くの先進的な企業が差別化のために能力の高いプログラマーを高報酬で雇い始めた。

ところが、日本企業の多くがその変化に気づいていない。個人の感覚としてもプログラミングを軽視する感覚が根強い。また、企業の採用・人事制度でもプログラマーは立場が低く扱われている。

というのが僕の認識だ。確かめたわけではないがそこそこ正しいだろう。

 

なぜ、このようなことが起こったのか。この先は完全にただの憶測だ。

まず、プログラミングが儲からなくなっていくと思われていたのはなぜか。それは製造業の影響だろう。製造業にはスマイルカーブという概念がある。企画→設計→製造→流通→マーケティング→アフターサービスという各工程の付加価値はお盆のような形をしており、両端の企画とアフターサービスが一番高く、次いで設計とマーケティングが高く、製造と流通は最も低い。この考え方をソフトウェアに当てはめるとプログラミングは製造工程に当たる気がしてしまうのだろう。ここから得られる教訓は製造業とソフトウェア産業は全然別物であるということだ。少しフォローするなら、これも伝聞だが、プログラム言語がアセンブラCOBOLなどしかなかった時代のソフトウェア産業人海戦術の域を出ておらず、製造業と大差なかったのだろう。そもそもソフトウェア産業などという概念自体がなく、それは製造業の一種と考えられていたのなら、そういう勘違いが発生するのも無理はない。

また、なぜプログラミングの価値が落ちなかったのか。これは大変興味深い謎である。僕がTLで見たのはこんな意見だ。

  1. 優秀な人とそうでない人の差が大きすぎるから
  2. 設計とコーディングが別工程だと思われていたが、実は不可分だったから
  3. ソフトウェア開発が高度化しているから
  4. プログラミング可能なものが増えたから
  5. プログラマー文化の本流はヒッピー文化でもあり、反権威的なプログラマーたちがそういう流れを作ったから
  6. シリコンバレーを中心とする経済圏が過熱しすぎていて相場を押し上げているから
  7. プログラミングは人間の限界を拡張する手段であり(エンハンスメントとかオーギュメントとか難しい言葉を使ってた)プログラミングを覚えた人間は一段上のステージに行くから

どれもあり得そうな話だ。僕は4を推しておく。

最後に、なぜ日本人はその変化に気づけなかったのか。この疑問を考えるには、そもそもアメリカでも気づかれていない可能性を考えなければいけない。今をときめく映画評論家町山智浩さんによればアメリカ人の半分はニューヨークの場所を知らないらしいので、Googleプログラマーが高給であることを7割くらいの人が知らない可能性は高い。であれば、日本だけが遅れているという前提すら危うい。まあしかし、いったんそこは置いておいて考える。まず企業の中では、多くの日本企業の昇進制度上、会社を動かしているのは10年以上前に活躍していた人が多いはずだ。つまり、そういう考え方でうまく回っていた時代の人だ。そうであればプログラミングの重要さを前提とした経営判断がし辛いのは想像できる。個人の問題としては、各国の経済成長の動向が大きいと思う。日本はこの数十年の間に途上国に次々と追い越された。日本が追いかける側の国であればアメリカでプログラミングがもうかっているらしいという情報が入った瞬間にみんながまねしたであろう。しかし、一度製造業で世界を制した成功体験があるだけになかなかそこから抜け出せない考えの人も多いと思う。

 

では、今僕たちはどうするべきか。お断りにも書いたが「現場を大事にするべきだ」みたいなわかりやすいスローガンに回収しないほうがいいと思う。もちろん、それはそうなのだが、残念ながら付加価値の多寡というのは必ずある。職業に貴賎はないが給料は違う。個人戦略としてどこが儲かるかを判断する必要はあるし、企業としてもどこで差別化するかを見極めて、誰がやっても同じな業務は安く抑え、勝因になりえる業務に強い人を雇うのは当然のことだと思う。少なくとも僕らはプログラマーとして能力を伸ばすべきだ。管理職やマーケ職の方が付加価値が高くなる可能性よりもプログラマーの付加価値が伸び続けるほうに張りつつ、しかし、それが逆転する兆候があればすぐに身を翻す機敏さも持ち合わせなければいけない。

ポリテックと型システム

金融とテクノロジーフィンテックで行政とテクノロジーがゴブテックなら、政治とテクノロジーの融合はポリテックだそうだ。

政治をよくできそうな技術として、どうも政治家さんにはAIがモテモテのようだが、型システムの方が味がいい気がする。

例えばこの意見を見てどう思うか?

最近少年犯罪が増えているので、若者は全員自衛隊に入って根性を鍛えるべきだ。

そういう考え方の是非はともかく、前提知識として少年犯罪自体は減っている。だから、最初からこういう意見は取り合わなくていい。

また、これはどうだろうか?

 自民党に投票しなかったということは民主党を支持するってことだな?

 これは必要条件と十分条件の区別がついていない人だ。

黙秘するということは犯行を認めるということですね?

これは三値論理と言う。三値論理的にこの主張は間違っている。

 

型システムを使えばこういう無駄な議論に時間を使わなくてよくなる。「わかりきった事実誤認に基づいた意見」「真偽や善悪以前に理屈が通っていない理屈」をあらかじめ排除できる。

どこか一箇所に型定義リポジトリを作りそこに自明の知識をプールしておけばいい。

「少年犯罪は減っている」「東京は出生率が低い」「女子高生が産婦人科に行く理由」「日大と日本体育大は別」など、間違えそうなことは事前に登録しておいてあげれば無知を晒して恥ずかしい思いをする人も減るだろう。

思い切って

  • 政治家は国会で型チェックを通らない話をしてはならない
  • 国民は政治家に型チェックを通らない要望をあげてはならない

というルールにしておけばいい。

 

逆に誰でも簡単に意見が言えてよくなるという効果もあると思う。

例えば、映画は犯罪率を低下させるのではないかと思っている人がいたとしよう。しかし、実際にそうなのか調査結果がどこにもなかったとする。こう意見を言ったとする。

映画には犯罪率を低下させる効果がある。経済発展のために映画を見るべきだ。

映画を見るべきだというのはその通りだと思うが、話の前後に繋がりがない。これでは相手にしてもらえない。

そこで

映画には犯罪率を低下させる効果がある。犯罪率の低下のために映画を見るべきだ。

とか

映画には犯罪率を低下させる効果がある。犯罪率の低下は経済発展に効果がある。経済発展のために映画を見るべきだ。

などであれば事実関係はともかく理屈は通っている。「じゃあ映画に本当に犯罪率低下の効果があるか、犯罪率低下に経済発展の効果があるか調べるから予算くれ」と話を繋げられる。このように事前に理屈を整頓する機会を与えられることによって、意見を言いやすくする効果があるのではないか。

 

 

 

ブラウザに自動ドネーションAPI機能がほしい

以前からコンテンツ提供者にもっとお金が支払われるべきだと思っていた。

こういう仕組みがほしい。

  • 僕がChromeに「毎月5,000円をコンテンツ提供者に支払う」と設定する
  • Chromeは俺の行動履歴からどのサイトにどのくらい滞在しているかを集計する
  • 月締めで滞在時間に応じて自動的に5,000円を各サイトに分配する

この方法が必要な理由はこうだ。

  1. たとえサイト運営者がPaypalなどでドネーションの申し込み口を設けていたとしても、いちいちそれをクリックするのがめんどくさい
  2. ある程度お金を払っていることを忘れられるUXでないともったいないと感じてしまう(i-Modeの論理)

 

人生の5箇年計画

やりたいことがいっぱいありすぎるので一覧を作る。

5箇年計画と書いたが、実際には何年かかるか全く見当がつかない。

 

  1. 数学検定準一級
  2. TOEIC800点
  3. レッドコーダーになる
  4. Webフロントエンドのベストプラクティスホスティングサービスを作る
  5. Dartのサーバーサイドフレームワークを作る
  6. ビジネスロジックからDBのクエリを自動生成するツールを作る
  7. ALT SQLを作る
  8. 数学検定一級
  9. Google Cloud Certified を取る
  10. 応用情報技術者をとる
  11. データベーススペシャリストをとる
  12. ネットワークスペシャリストをとる
  13. セキュリティスペシャリストをとる
  14. LPIC Level3になる
  15. この辺で年収1000万になる
  16. CPUを作る
  17. OSを作る
  18. 言語を作る
  19. RDBの理論面を勉強する
  20. 型システムの理論面を勉強する
  21. 素晴らしい数式処理システムを作る
  22. 暗号論を勉強する
  23. 新しいWebの仕様を策定しバーナーズリーの後継者になる
  24. 絵を描けるようになる
  25. 何か楽器ができるようになる

30才までに終わるかな。