読者です 読者をやめる 読者になる 読者になる

実例に基づいた大規模 iOS アプリの継続的な開発についての勉強会を開催しました

f:id:nano-041214:20161201191121j:plain

技術部モバイル基盤グループ新卒エンジニアの日高(@natan3)です。

去る11月17日、「Cookpad Tech Kitchen #4 〜Cookpad × MoneyForward〜」と題して、iOS エンジニア向けの技術交流イベントを行いました。

https://cookpad-tech-kitchen.connpass.com/event/43082/

このイベントでは、 iOS開発の中でも特に大規模アプリの品質を維持するための設計や、複数の言語圏や様々なパートナー企業に合わせたアプリの提供をテーマに、弊社のエンジニアから2つ、マネーフォワードさんから2つの発表がありました。

この記事では、その様子についてお伝えします。

iOSアプリケーションの海外展開

まず、海外事業部の西山(@yuseinishiyama)から、海外事業向けのiOSアプリケーションの開発フローについて紹介がありました。

How to make your app international by Yusei Nishiyama Published November 18, 2016 in Programming

この発表では

  • 言語圏に合わせたコンテンツや UI のローカライズ
  • RTL 対応
  • その他アプリのローカライズに関する Tips

などについて紹介しました。

私個人としては、普段日本向けの開発しかしていなかったので、アラビア語圏への対応などとても興味深かったです。

また、グローバルプラットフォームならではのサービス開発に対する取り組みは以下の記事でも紹介しています。

海外のユーザーを向いたプロダクト開発の工夫 iOSアプリケーションの国際化と地域化

トクバイ新アプリと Swift と

次に、クックパッドの子会社であるトクバイの行川(@hatuyuki4)から、トクバイアプリがどう Swift で開発を進めているかについての発表がありました。

トクバイ新アプリとSwiftと by Hatuyuki4 Published November 21, 2016

この発表では、Swift + RxSwift + MVVM の実装について具体例を交えながら紹介しています。

リアクティブな処理にすることで複雑なコールバックによる辛さから開放されたり、UnitTest が書きやすくなったりと Rx のメリットがわかりやすく示されているので、Rx 導入を考えている方にぜひオススメしたいスライドでした。

Ruby on Rails にはなりますが、react-rails を用いたアプリを開発する際の知見についてはこちらの記事で紹介しておりますので、興味の有る方はぜひこちらも御覧ください。

非SPAなサービスにReactを導入する

iOS Clean Architecture のすすめ

3番目の発表では、マネーフォワードさんの iOS エンジニアである児玉さんから、マネーフォワードではどのような設計で iOS アプリを開発しているのかについての発表がありました。

iOS Clean Architecture のすすめ by koutalou Published November 17, 2016 in Technology

この発表では、Clean Architecture の実装例と導入する際のメリットを紹介しています。

児玉さんの Github アカウントで iOS-CleanArchitecture のサンプル が公開されているので、私も試してみようと思いました。

児玉さんが執筆されたマネーフォワード エンジニアブログの記事はこちらにありますので、tvOS やマネーフォワードの開発体制に興味がある方はぜひこちらも御覧ください。

マネーフォワードのtvOSアプリケーションを開発した話

マネーフォワードの変遷と「パートナー企業版」の展開

最後に、マネーフォワードさんの iOS エンジニアで、取締役の都築さんから、マネーフォワードがどのようにアップデートを重ねてきたかについて発表がありました。

マネーフォワードの変遷と「パートナー企業版」の展開 by Takayuki TSUZUKI

この発表では、マネーフォワードさんがかつては Titanium で開発されていたという意外な事実や、パートナー企業版の開発で起きた問題をどうやって解決したかについて紹介しています。

アプリのリニューアルはとても大変な作業なので、きっと色々な苦労を経て今のマネーフォワードさんのアプリがあるんだなと感じました。

都築さんが執筆されたマネーフォワード エンジニアブログの記事はこちらにありますので、マネーフォワードさんのアプリのリニューアルについてもっと詳しく知りたい方はぜひこちらも御覧ください。

iOS App資産タブリニューアルを終えて

質疑応答

f:id:nano-041214:20161201191148j:plain

質疑応答では、

  • Clean Architectureの導入に伴う初期コストをビジネスサイドにどのように説明したか
  • Rx を実際に取り入れる過程や導入後の開発効率の変化

に関した内容の質問に対して

  • 複雑化してメンテナンス性が下がったコードに対して、開発効率が10倍になる!などで熱意を示してきっかけを作ったこと
  • 全員が理解していればすごく効率が上がるが、移行期には同じ処理を二つのアーキテクチャで書く必要があるなどの問題もあること

といった回答を得られました。質疑応答も大変盛り上がり、とても有意義な時間になったと思います。

懇親会

上記の発表の後、懇親会を行いました。

懇親会では、他の弊社の iOS アプリエンジニアやデザイナも参加しながら、参加者と様々な意見交換や雑談をしました。

多くの皆様にご参加いただけたおかげで、iOS 開発にありがちなことで盛り上がったり、各社の iOS 開発の知見を共有するなど、有意義な時間を過ごすことができました。

今回は合同イベントということで、弊社のロゴとマネーフォワードさんのロゴを用いた押し寿司も振る舞われました。

f:id:nano-041214:20161201191234j:plain

まとめ

いかがでしたでしょうか。

クックパッドでは、今後もエンジニア向けのイベントを行っていきたいと考えています。来る 2017 年 1 月 21 日には、 Cookpad TechConf 2017 を開催する予定です。

また、クックパッドでは、一緒に iOS アプリ開発を行っていくエンジニアを募集しています。ご興味の有る方は是非遊びにいらしてください。

最小限にこだわるサービス開発の試み

こんにちは、検索事業部の原田です。クックパッドでプロダクトマネージャーとして検索領域を中心にサービス開発に携わっています。

サービス開発を成功に近づけるためのフレームワークやプロセスについては、書籍等で多くの知見が紹介されています。こちらのブログでもクックパッドの例についてご紹介をしてきました。

クックパッドで大事にしているサービス開発の取り組みのひとつに「仮説をスピーディーに最小限の形で確かめる」というものがあります。言うは易しなのですが、そもそも取り組むべき最小限の形とは? それをどうやって見つけるのか? やっとの思いで何かを見つけられたとして、その後最小限にこだわり判断し続けるには? と、大変な困難が伴うと感じています。

今回は、サービス開発プロセスの中でも特に、どのように取り組むべき最小限の形を見つけ、最小限にこだわり続けながら開発を進めるのかについて、私が気をつけていること(気をつけたいと考えていることも含みます)をご紹介したいと思います。

取り組むべきサービスの判断基準

本題に入る前に前提の話として、そもそも取り組むべきサービスとは何でしょうか。会社の状況や責任範囲により異なりクックパッド内でも考え方は様々で一律の基準は提示できませんが、私の場合は以下のような着眼点を持つことで目指す的をなるべく狭くできるようにしています。

  • それは料理を楽しくするかどうか(情熱をもって取り組めるものか、ユーザーにとって価値のあるものであるか)
  • それは誰にも負けないことかどうか(実現可能なことか、世界一になれる部分はどこか)
  • それは儲かるかどうか(経済的な原動力になるのは何か)

この三つの重なりを意識することが、後々最小限を維持するための拠り所になります。サービス開発に限った話ではありませんが、このような考え方は、書籍『ビジョナリーカンパニー2~飛躍の法則~』の中では「針鼠の概念」として紹介されています。もちろん、初めから三つを重ねるようなアイデアはないことがほとんどです。重ならなかったら着手しないということではなく、開発していく過程で3つを重ねることを常に問い続けるプロセス自体が大事だと考えています。

最小限であることがなぜ大事なのか

ここからが本題なのですが、そもそもなぜ最小限にこだわる必要があるのでしょうか。反対側から考えてみると、最小限でないことにより以下のようなことを失うのではないかと考えています。

  • ユーザーに届くまでのスピード(製品化に至るまでには時間がかかる、機能が多ければそれだけ確かめるまでが遠くなる)
  • コア機能に集中できるメンバーの時間(一緒に働きたいと思うメンバーは常に有限。機能を足す判断をするごとに、最初に定義した取り組むべきコアな部分を開発できるメンバーを失う)
  • 検証のシンプルさ、明確さ(選択肢が多い=ユーザーは判断をせねばならない。ユーザーの生活は十分に忙しいはず。どの機能を使うかの判断をしなくてはならないようなサービスが本当に愛してもらえるかを考える必要がある)

機能を広げる判断をするときには、必ず同時に対で何かを失う判断をしているということを認識するようにしています。

最小限はなぜ難しいのか

初期の段階では、慎重に最小限の形をイメージしていることが多いと思うのですが、プロセスが進むにつれて最小限にこだわることへの難易度が上がっていきます。例えば具体的には以下のような状況が生まれます。

  • 関わるメンバーが増え、より良いアイデアについて議論の場が生まれる。
  • プロトタイプを使ったユーザーからこういう機能があったら便利、ここが不便、という具体的なフィードバックを得る場が生まれる。

上記の例はどちらも、サービス開発を進める上で必要であり不可欠あることは疑いようがないですが、機能を広げる方向に議論がおきやすくなるのは事実です。そもそも多様なニーズをサポートしていないという背景とともに、実現すべき体験・価値といったものの言語化が困難なことが機能追加へのプレッシャーを加速しがちです。上記の例で言うと一つ目のほうは「最小限とは?」の定義が不十分であれば当然は議論は発散しますし、二つ目のほうは利用者の具体的な声は説得力があるのに比べて、機能追加をするデメリットは言語化しづらいことが多いのではないでしょうか。

取り組むべき最小限を見つけ、集中し続けるために

ここまでは最小限であるべき理由の話でしたが、次は最小限の形を見つけるために具体的に実践していることをご紹介します。

企画発想:現実世界の代替手段を注視

企画発想の時点で価値を定める時に「機能ではなく体験をイメージする」という話があります。しかしそもそも体験を言葉にするのはとても難しいことです。

そこで、今その問題を抱えている人が現実世界で行っている解決方法に着目するようにしています。そのことで、言葉に表現せずともメンバー間で議論しようとしている体験のイメージを揃えることができるようになります。現実世界というところがポイントです。もし現実世界での解決方法がないのだとすると、それは取り組むべき十分切実な問題ではないかもしれないこともチェックできます。

例えば、献立に困っているユーザーさんは今どういった解決方法をとっているか? という問題に着目した時に、「Googleで検索している」だと、スマホを手にして情報を探索する体験に縛られてしまいます。現実世界で考えるとどうなるか。例えば、「本屋さんで棚にある雑誌をざーっとながめ、手に取りぱらぱら〜っとする」「母親に電話してアレどうやって作るの?と聞く」「給食の献立表を眺める」という具合になります。

そうすることで上記の例では以下のように体験を具体的にイメージすることができるようになります。

  • 「ざーっとながめたり、ぱらぱら〜っとする」という体験にはどういう意味があるのだろうか→一度にバッと見られることって何か意味がありそう
  • 電話は友だちに訊ねるではダメなのか?→母親に「あのよく出てたタコの炒めた感じのアレどうやって作るの?」って、家でよく出た「アレ」は名前がはっきりしていなかったりする
  • 給食の献立ってレシピないけれどどういうことなんだろう?→給食の献立表ってメニュー名だけだけれどなぜか大体どんな味か想像できるな

このように現実世界を注視することで、言葉にしにくい体験を具現化しやすくなり議論の発散を防いでくれると考えています。

施策開始前:最小限の形を見つけるためのプロトタイピング

上記で定義した体験を製品に落とし込んでいくわけですが、多くの場合はもやもやしながらスタートします。私は、この時点でコンセプトなど言語化の詰めを急がないようにしています。なぜなら、言葉の稚拙さのせいで可能性を閉じてしまうことがあるからです。その代わりに最小限の形を見つけるためのプロトタイピングを行っています。

プロトタイピングするというと、施策をスタートさせた後に行うイメージがありますが、私はコンセプトを決めた後に行うプロトタイピングと、取り組むべき最小限の形を見つけるためのプロトタイピングをわけて考えるようにしています。今回の題材である「取り組むべき最小限の形を見つけるためのプロトタイピング」は、言語化することの限界を超えるためのプロセスとして考えていて、ゴールは技術やビジュアルの仕様を固めることではありません。「私だったら今まで週1回しか使っていなかったけれども毎日使っちゃう」という感覚に、自分以外の人が触わり議論をスタートできる状態にできることがゴールです。ここまでは、言語化が必要ない人数で進める必要があります。

上記の状態を達成できてはじめて、施策化し開発を開始するかどうかの判断ができます。裏を返せば、上記の状態が達成できなければ「開発をしない」という判断をするということです。そういう意味で、開発スタート後のプロトタイピングとは大きく役割が異なります。

施策化:圧倒的な「売り」を見つけて言語化・数値化

もやもやとしたアイデアをめでたく 「私だったら今まで週1回しか使っていなかったけれども毎日使っちゃう」という形にできたところで、ようやく言語化に取り組みます。仲間も増えて開発をスタートできるこのフェーズでは、以下の問いに対する解をもった言葉(=売り)を持つことが、最小限からブレない判断をサポートしてくれます。

  • 利用者の生活をどう変えるか?
  • 競合と比較して圧倒的に差別化された利点は何か?

「売り」の概念や言語化については今回は詳しくご紹介しませんが、ご興味がある方は、『「売れ顔」の法則』をはじめとした消費財におけるマーケティングに関する情報がとても役立ちますのでご覧ください。

売りを一言にできたところでその言葉を数字で表現し、あとは数字を上げるよう行動します。 私の場合はこのように整理して共有したりしています。

f:id:a_harada:20161124182810p:plain

この後も常に機能追加のプレッシャーを受け続けることになるため、取り組むべき最小限にこだわるために何かを判断する時は以下のことに気をつけています。

  • それは数値を10倍にするか?
  • それは絶対に他の人も毎日使うはず、と思えるか?「増えそうだ」「良さそうだ」ということになっていないか?

なお、「数字を上げるよう行動する」についてはこちらブログでも紹介されている「新サービス立ち上げ時の重要指標のデザイン」等に詳しいので、ぜひご覧ください。

施策開始後:最小限以外の事項は、検討の「解禁日」を設定

開発を開始できるとコアな価値自体とは関係がなくても、サービスを届けるためには検討が必要な事項や機能が必ず出てきます。例えば既存のサービスのどこからアクセスしてもらうか、そこに今入っている機能とどう折り合いをつけるか、といったことがその一例です。これは検討しているサービスが公開へのフェーズに向かっている証拠であり、喜ばしい事態です。

同時に、例えば既存機能との調整は特にもやもやとした新しいサービスの価値よりもはるかに具体的にややらなくてはいけないことがはっきり可視化できるゆえ、まだ最小限の形ができてないうちから時間を使ってしまうことが問題です。結局ユーザーに使われなければいくら調整がうまくできても本末転倒、という事態を避けるために、この案件はここの日から考え始める、という「解禁日」を設定しスケジュールに明記して周囲に宣言するようにしています。そのことで、それまでは最小限の実現だけに集中できる環境を用意することができます。

「最小限」かどうかの確かめ方

あなたが今取り組んでる施策の「ターゲットでない人」、瞬時に言えますか?

もしも途中で「今最小限の形で進められているかな?」と不安になることがあったら、この問いを思い浮かべてみてください。瞬時に具体的に答えられない場合は危険信号。最小限の製品ではターゲットは限られた人であり、ターゲットではない人が明確に存在するはずです。

まとめ

ゼロからのサービス開発でも既存サービスに対する開発であっても、機能を広げない判断は大変です。良さそうな機能をどんどん追加できるなら… という誘惑にいつも揺らぎそうになります。しかし、機能を追加する判断をすることは、その機能が良いとか悪いということとは別に、必ず対でユーザーに判断という負担を強いること、開発する仲間の集中を失う判断をしているということに他なりません。開発する仲間の力が分散することは、サービス改善が遅れる、もしくはそもそもできないという形でユーザーに反映されます。

気がついたら複雑すぎてメンテナンスされていない機能がたくさんあるというような不幸な状態は誰も望んでいないはずですが、前述のように言語化に限界がある以上そうなりやすい状況は必然であることを前提に、判断をサポートする仕組みづくりに取り組んでいきたいと感じています。

最後になりますが、クックパッドでは既に存在するものにとらわれない視点でサービス開発に取り組むことに興味のある方を募集しています。ぜひご応募ください!

分析SQLのコーディングスタイル

SQL、書いてますか?

こと大規模データ処理の分野においてはSQLはもはや標準インターフェイスであり、 分析やらバッチやらに関わっている皆様は日々大量のSQLクエリーを生産していることと思います。

そこでちょっと気になるのが、 SQLのコーディングスタイルってどうするのが一般的なんだっけ……? という点です。 イマドキはSQLなんてO/R mapperに吐かせることが多いからなのか、 それともコードを広い範囲で共有することがそもそもないからか、 SQLのコーディングスタイルについて見聞きすることは他のプログラミング言語に比べるとだいぶ少なく、 いまいち決定版と言えるスタイルがないなと感じています。

そんなわけで本日は、SQLのコーディングスタイルについての意識を活発化させるべく、 クックパッドでわたし(青木)が使っているコーディングスタイルから特徴的な点を紹介したいと思います。 特に、分析バッチで用いるような巨大なSQLのスタイルについて話します。

なお、この記事では、コーディングスタイルを網羅的に示すことはしません。 いまさら「演算子の周囲には空白文字を置く」やら「コードはインデントする」のように どうでもいい(積極的に抵抗する人がいない)スタイルについてえんえん書くのは 時間の無駄でしかありませんし、基本方針がわかりにくくなるからです。 意見の分かれやすい、抵抗の大きそうなところに集中して述べていきたいと思います。

サンプルコード

まず、わたしが普段採用しているコーディングスタイルをお見せします。

次のクエリーは、適当にそのへんのファイルからSQLを拾ってきて、適当にテーブル名やカラム名を変えたものです。 ある程度の長さがないと事情が理解してもらえないと思うので、まあまあ長めのものにしました。 なお、この記事の内容にはクエリーの意味は関係ないので、読む必要はありません。スタイルだけ見てください。

select
    user_id
    , user_session_id
    , min(log_time) as session_start_time
    , max(log_time) as session_end_time
    , count(*) as num_steps
    , max(case session_step when 1 then keywords else null end) as step1
    , max(case session_step when 2 then keywords else null end) as step2
    , max(case session_step when 3 then keywords else null end) as step3
    , max(case session_step when 4 then keywords else null end) as step4
    , max(case session_step when 5 then keywords else null end) as step5
    , max(case session_step when 6 then keywords else null end) as step6
    , max(case session_step when 7 then keywords else null end) as step7
    , max(case session_step when 8 then keywords else null end) as step8
from (
    select
        user_id
        , user_session_id
        , row_number() over (
              partition by user_id, user_session_id
              order by log_time
          ) as session_step
        , log_time
        , keywords
    from (
        select
            user_id
            , sum(session_delta) over (
                  partition by user_id
                  order by log_time
                  rows between unbounded preceding and current row
              ) as user_session_id
            , log_time
            , keywords
        from (
            select
                user_id
                , case
                  when
                    lag(log_time) over (partition by user_id order by log_time) is null
                    or log_time > lag(log_time) over (partition by user_id order by log_time) + interval '00:30'
                  then 1 else 0 end as session_delta
                , log_time
                , trim(word1
                      ||' '|| coalesce(word2,'')
                      ||' '|| coalesce(word3,'')
                      ||' '|| coalesce(word4,'')
                      ||' '|| coalesce(word5,'')
                      ||' '|| coalesce(word6,'')
                  ) as keywords
            from
                activity.search_log
            where
                log_time between timestamp '2015-02-01 00:00:00' and timestamp '2015-02-01 01:00:00'
        )
    )
)
group by user_id, user_session_id
order by user_id, user_session_id
;

このスタイルは会社の先輩が使っていたスタイルが便利だったのでそれをベースに、 ウィンドウ関数やcase式のスタイルを追加・改良したものです。 ここから主な論点として、次の7点に注目したいと思います。

  1. 大文字と小文字の使い分け
  2. カンマの位置
  3. セミコロンの位置
  4. select、from、whereを左右どちらに寄せるか
  5. joinのインデント
  6. インデント幅
  7. 標準SQLとの向き合いかた

いかにも炎上しそうな項目から順に述べていきましょう。

1. すべて小文字を使う

SQLのコーディングスタイルについて語るとき、最初にして最大最悪の障害は大文字小文字の使いかたではないでしょうか。

JavaやRubyのように比較的新しいプログラミング言語はあまり大文字を使わない傾向にあります。 しかしSQLはメインフレーム全盛の時代から生き残っているだけあって、 大文字アルファベット文化がいまなお大きな影響力を持っています。 そのため、SQLを古くから書いている人ほど大文字をよく使うように感じています。

大文字小文字のスタイルを大きく分類すると、以下の3つに分けられるでしょう。

  1. すべて大文字
  2. キーワード(予約語)は大文字
  3. すべて小文字

わたしのスタイルは3の「すべて小文字」です。

まずスタイル1ではない理由は簡単です。 大昔からSQLを書いている人ならともかく、このサイトを見るような読者層にとってみると、 「すべて大文字」というのは、ほぼ誰もやりたくないスタイルと言ってよいでしょう。 個人的にも、正直すべて大文字のコードは読みたくありません。 よってこのスタイルは最初から検討もしませんでした。

次に2の「キーワードは大文字」について。これはおそらく最大派閥ですが、わたしはこのスタイルは採用しません。

この派閥を支持する意見としては、「大文字のほうが見やすい」という主張をよく見ます。 ですがこれは大変怪しい主張です。

「大文字は見やすいからキーワードのように構文上重要な役割を持つ語は大文字である」……と本当に考えるのなら、 JavaやRubyについても同じことを言うべきです。 しかしJavaやRubyで予約語が小文字であることに対して文句を言っている人は見たことがありません。

なぜか。小文字で十分見やすいからです。

最近はどんな開発環境だろうともシンタックスハイライトくらいは完備しているでしょうし、 わざわざ大文字にせずとも視認性は十分得られます。 あえて大文字にする理由は、歴史的な理由以外にはないと思います。

「おまえは今まで使ったSQLのキーワードがいくつあるか覚えているのか?」

しかしもちろん、「キーワードだけ大文字」に見やすさという理由がなかったとしても、歴史的な継続性はあるわけです。 少なくともわたしの主張する「すべて小文字」派よりは多少なりとも昔の面影を残しているぶん、 古くからSQLを書いている人たちにもアピールするという利点はあるでしょう。

ですが、それでもわたしが「キーワードだけ大文字」を採用しないのは、このルールがあまりにも厄介だからです。

SQLには、他のプログラミング言語に比べて遥かに多くのキーワードがあります。 selectやfrom、join、asなどは当然として、 関数名もたいていキーワードなのでcountもmaxもsumもキーワード、 ウィンドウ関数のrankもrow_numberもキーワード、rowやunpreceedingもキーワード、 extract関数で使うyearやmonth、dayなどもすべてキーワードです。 あまりにもキーワードが多すぎて、たぶん誰もすべてのキーワードを覚えていません。

特にuserやactionのようないかにもよく使いそうな単語がキーワードなのが最悪で、 これらが現実的な確率でカラム名として使われてしまいます。 そうなったときに、こいつらも大文字にするのか、クオートだけして小文字にしてしまうのか、 といったしょーもないことで悩む必要が出てきます。

また、RDBMSごとの特有のキーワードというものも存在します。 しかしそういったDB固有のキーワードはエディターがサポートしていないことが多く、 DB固有のキーワードだけは小文字にされてしまう……みたいな悲しい事態に陥ります。 UDF(User-Defined Function)やUDT(User-Defined Type)のような、 ユーザーが独自に定義する関数や型も同様の問題を起こします。

具体的にはこんな感じの見ためになるわけです。

SELECT
    "USER"
    , COUNT(user_id)
    , some_udf("USER")
    , EXTRACT(YEAR FROM created_at)

これが、本当に、見やすいのでしょうか?

もう正直気持ち悪い面倒だわ、どれがキーワードだか覚えられんわで、いいことは何一つないように思われます。 ここまでやるなら「すべて大文字で書くよ」派のほうがまだ一貫性があって楽かもしれません。

真面目に従えない使い分けルールを決めるくらいだったら、そもそも大文字小文字の使い分けをやめたほうがまだマシです。 そして大文字と小文字のどちらかに揃えるとしたら、小文字のほうが幸せになれる人が多いはずです。

2. 改行前後のカンマと演算子は次の行の先頭に置く

次の話題に行きましょう。

カンマや演算子のところで改行する場合は、次の行の先頭にカンマや演算子を置きます。 つまりこの部分ですね。

   select
        user_id
        , user_session_id
        , row_number() over (
              partition by user_id, user_session_id
              order by log_time
          ) as session_step
        , log_time
        , keywords

andなどの論理演算子も次のようにすべて先頭に置きます。

where
    hst.user_id is null
    and hst.updated_at is null

このスタイルを選んだ理由は、記述しているカラムや条件のかたまりが最もわかりやすいからです。

最初のコードを見てもらえばわかるように、ウィンドウ関数やcase式が入ってくると、 インデントだけではselect文にカラムがいくつ書かれているのかよくわからなくなってきます。 しかし不幸にしてSQLではカラムの順序に大変大きな意味があるので、 何カラムめに何の値が入っているかというのはかなり重要な情報です。 そこでカンマや演算子を先頭に並べて書いておくと、それを数えるだけで項目数がすぐわかるわけです。

カンマを前に置くスタイルについては編集上の利点を挙げる人もいますが、わたしは特にそこは重視していません。 あくまでカラムの数と位置のわかりやすさを重視します。

もっとも、演算子はともかくとして前カンマについては、単純に気持ち悪いと感じる人が多いでしょう。 正直わたしも最初のうちは「先頭キモいな〜、これ本当に前に書くのかな〜」と思っていたのですが、 ちょっと面白かったので試しに3日間書いていたら慣れました。 わりと簡単に慣れるので試してみてください。

ちなみに、他のプログラミング言語でも例えばLispのマクロでは前カンマを使いますし、前例がないわけでもありません。 プログラミング言語は自然言語ではないのだから、必ずしも自然言語由来の自然さにこだわる必要はないと思っています。

3. 文末のセミコロンは単独行に置く

3つめの話題。文末のセミコロンは単独で、別の行に置きます。 つまり次のように書くわけです。

select ...
from ...
where ...
;

これは実はあまり大きな理由はなくて、カンマの前置に揃えているだけです。 強いて言うと、psqlなどのコマンドラインクライアントにコピペするときにセミコロンだけ抜いてコピペがしやすい、という利点があります (セミコロンさえ打たなければCtrl-Cでキャンセルできるので、変な失敗が起きにくいのです)。

4. 各句のキーワードはインデントしない

4つめの話題。 ちょっとあまりに面倒くさすぎて信じられないのですが、 世の中には次のようにキーワードを右寄せにしたがる派閥も存在しています。

select count(*)
  from mst.users
 where user_id = 5;

わたしはsyntax off; set sw=4 ai smd ts=8 etですべてのエディタ設定が完了する オールドタイプ人類なのでさすがにこれに付き合う気はありません。 select、from、where、order byのレベルはすべてインデントを揃えます。

そもそも、この右寄せスタイルの効果があるのはそうとうに短いクエリーだけです。 サブクエリーのネスト5段、1 selectごとにカラムが10個……みたいな、 分析系ではよくありがちなクエリーには無用の長物でしかありません。 むしろ各句の左端が揃わなくなるため、サブクエリーのネストレベルがわかりにくいという欠点が目立ちます。

そして何よりも、キーワード右寄せスタイルは「select for locking access」のとき (あるんですよ世の中にはそういう文が……)に26文字インデントしなければならないのが最悪だと思います。 こんなことになるわけです。

select for locking access count(*)
                     from mst.users
                    where user_id = 5
;

カラムの右揃えは空白文字と時間の無駄です。黙って左に揃えましょう。

5. join式はfromよりもインデントする

5つめの話題はfrom句内のインデントについて。

ジョインが存在する場合のインデントも判断の分かれやすいところです。 わたしが採用しているスタイルは次のように、joinの記述をfromよりもインデントします。

from
    work.weighted_segments s
    inner join source.factor_score_flg f on s.guest_user_id = f.user_id
    inner join source.factor_score_mst m using (attr_id)

一方で、次のようなインデントスタイルも根強く存在します。

from work.weighted_segments s
inner join source.factor_score_flg f on s.guest_user_id = f.user_id
inner join source.factor_score_mst m using (attr_id)

ですが、このスタイルはわたしに言わせればインデントの役割を完全に放棄しているとしか思えません。

上記の式では3つのテーブルをジョインしていますが、その場合、3つのテーブルをジョインしたリレーション全体がfrom句の値です。 1つめのテーブルだけではありません。 ならば、文法の内包構造をインデントに表現しようとした場合、3つのテーブルと「inner join」は すべて「from」より下位にあるべきで、「from」と「inner join」を同列に並べる選択はありえないでしょう。 そもそも「from」は句ですが、「inner join」は二項演算子です。その時点で両者は対等ではありません。

「何もインデントしない」スタイルを採用するのでない限り、 from句に含まれるコードはすべてインデントすべきです。

6. インデントとネスト、そしてwith句について

6つめの話題はようやくインデントの深さについてです。 わたしのスタイルではSQLのインデントは空白4文字にします。このへんは趣味です。

強いて4スペースにする理由を挙げるなら、バッチではかなりネストの深いサブクエリーを使うことが多いので、 2スペースくらいだとすぐに見分け付かなくてきつい、くらいでしょうか。 4スペースでもよくわからなくなるときがあります。 しかし8スペースまで深くすると今度は200桁くらいになってしまうことがあり、さすがに深すぎます。 SQLの場合はCで関数を分けるような気軽さではサブクエリーを分離できないので、回避しにくいことも問題です。

ちなみにサブクエリーの代替になりネストも減っていいじゃんと話題のwith句ですが、 わたしは実行プラン(explain)を見るまでは絶対にwith句にはしません。 プランナーを信用していないからです。 基本的に、RDBMSのプランナーは新しい構文を使うほどよくしくじります。 新しい構文は用心してかかるのが得策だと考えます。

またSQLバッチに関して言えば、withを使って見ためのネストだけ下げるよりは、 ビューを作ったり、ワークテーブルを追加して対処したほうが見通しがよくなるだろうとも思います。 with句だろうがサブクエリーだろうが途中経過は人間には見えないので、 巨大なクエリーでは開発・運用が難しいことに変わりはないからです。

7. 標準SQLのこと、忘れてください……

最後に標準について。

ことSQLに関して言う限り、ポータビリティを考えてANSI標準だけを使うなどという選択は間違いです。 少なくとも、現実的ではありません。

そりゃあもちろん、O/R mapperが生成する類のこの程度のクエリーなら問題はないでしょう。

select * from entity_table where id = 230985;

そしてこれ以上に複雑なほとんどすべてのクエリーが標準をはみ出すことを覚悟しなければなりません。

……いや、それはさすがに言い過ぎでした。言葉の綾というやつでした。

ですが例えばPostgreSQLのリファレンスマニュアルから適当な構文のページを開いて、 「この構文はPostgreSQL独自拡張です」のフレーズが何回登場するかを数えてみてください。 インデックスを張ったとたんに、upsertをしたいと思った瞬間に、ちょっと特殊な制約を付けたいと思ったばかりに、 サクッとANSI標準から踏み出すことを覚悟しなければならないのがSQLの現実です。 そして、標準の範囲内ですべての処理を書くことよりは、DB固有拡張のほうが必要性が遥かに上です。 標準にこだわってDB固有拡張を避けるのは悪手と言わざるをえません。

そもそもポータビリティを考えるという場合、DBを乗り換えることが前提ですが、 DBをプロジェクトの途中で変えることなどまずありません。 これはむろん鶏と卵の関係にあります。SQLがまともに標準化されていないからDBを乗り換えることができず、 どうせDBを乗り換えることはできないから標準なんてどうでもいいのです。 たいへんウンザリします。

各種RDBMSの差がもうちょっと少なくなってもらえないかという気持ちはやまやまですが、 いまのところは標準は「使えるときだけ使う」のが現実解でしょう。

おわりに

この記事では、わたしがクックパッドで使っている分析SQLのコーディングスタイルについて、7点に絞ってお話ししました。 コーディングスタイルはいつの時代も戦争の引き金になるので、いま大変嫌な予感がしています。 みなさんの手元ではどんなスタイルが使われているのか、はてブコメントなどでお寄せいただければ幸いです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/