>100 Views
December 21, 15
スライド概要
STAC2015の講演3での発表資料です。
2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp
広告システム刷新 よもやま話 テストが当たり前になる までにやったこと ヤフー株式会社 MSC開発本部 マーケッターPF開発部 森下⼤大介
• 名前:森下⼤大介 ⾃自⼰己紹介 • 年年齢:40 • 職歴:2011年年にYJに中途⼊入社 • ⾔言語:C/C++/Java YJ⼊入社前 YJ⼊入社後 Sierな世界の住⼈人。 BtoBなWebサービス開発へ。 受託案件をこなす業務システム開発 のエンジニア。 Web系と⾔言いつつエンタープライズ 系っぽいシステムの開発に携わる。
今回のお話の前提 BtoBでエンタープライズシステム的。 ヤフーというWebサービス会社の中 でも、現場はかなりSIerよりな雰囲 気があるところ。 ⾃自分の所属部署における 取り組み事例例。 そんな開発部署で、利利⽤用技術 や道具を⾃自分たちで決めたら こうできた的な感じのお話。
本⽇日の内容 P • バックグラウンド • 取り組んだこと • やったことその0:体制づくり • やったことその1:プログラミング⾔言語を変える • やったことその2:テストができるアーキテクチャに • やったことその3:テスト向けDSL (Spock) • やったことその4:CI/CD, 静的解析 • やったことその5:インタフェース定義⾔言語 • 振り返っての所感 4
バックグラウンド
例例1:⼩小改修なのにつらい 当時困っていたこと 「なんか妙につらい」 typo修正したいだけなのに・・・ -‐‑‒ ユニットテストがない -‐‑‒ コンパイラ、型がない -‐‑‒ IDE使ってない 例例2:結合がつらい 例例3:なんとなく動いてつらい なかなかつながらない・・・ LLの柔軟さが裏裏⽬目に・・・ -‐‑‒ -‐‑‒ 存在しないメソッドコールしても動く。 -‐‑‒ 間違った型で引数受け取っても動く。 -‐‑‒ -‐‑‒ APIIFドキュメントが⼿手書き、書き⽅方が統⼀一され てると⾔言えない APIIFドキュメントの書き⼿手と読み⼿手でお互いに 認識識がズレてる APIIFドキュメント通りに実装されていない。
考察1:「⼤大規模」で「複雑」 何が起きてる? サービスが育った結果・・・ -‐‑‒ 道具の助け無しで -‐‑‒ ⼈人⼿手と記憶と努⼒力力と根性で -‐‑‒ がんばれる規模をこえている 考察2:道具とやり⽅方が合ってない 考察3:積み重なる技術負債 ちゃんとデータを扱いたいのに やり⽅方そのままで -‐‑‒ 型宣⾔言が無い -‐‑‒ コンパイラに頼れない -‐‑‒ テストを仕掛けられない -‐‑‒ 既存コンポーネントは拡張され -‐‑‒ 新規コンポーネントが追加され -‐‑‒ そしてそこにテストは無い
そんなとき イチから全部変えろ! 組織変更更後の 部⾨門⻑⾧長より という無茶茶ブリ 機会をいただいた。 こういうキッカケは⼤大事。
⽬目指したこと 3倍早い開発スピード 変わるべきは「⾃自分達」 システム刷新は「結果」 • 3倍早く • やりやすく • 確実に • ⾃自分の変化がアウトプットを変える。 • でも結果を出すことも重要。 • 結果で証明する。 開発できる強いエンジニアになること。
取り組んだこと
やったことその0 体制づくり その1:リード役の配置 ⾃自分が当時の役職を離離れて、部⻑⾧長 直下のぼっち「部付」となり、刷 新活動に集中。 その2:仲間づくり その3:部⾨門⻑⾧長による宣⾔言 同じような問題意識識を持つ⼈人に声 をかけて、バーチャルなチームを 構築。 刷新していくことを部の内外でス テークホルダー/部内メンバーに 宣⾔言。
やったことその1 プログラミング⾔言語 を変える。 「⼤大規模で、データをきっち り扱うシステム」 をちゃんと開発するために必 要な変化だと判断。 これを決めた時点で学習コス トが極⼤大化するのは確定。 ⼤大抵はそうしないで済む⽅方向 を考えますが・・・ とにかく欲しかったのは • コンパイル! • 型宣⾔言!
求めた効果は、 しょうもないレベルの間違い を動かすまでもなく潰せるこ と。 この条件でいくと、現実的な 選択肢はだいぶ絞られる。 これができないと、typo修正 すらおそろしくなる。 • 動かして全確認が必要 • 漏漏れが無いか確証が持てない。 考えたのは以下あたり。 • C++ • Java • Scala
選んだのは Java Webサービス系開発者に はとかく嫌われる、ディ スられるが・・・ これで得られたもの 副次的なものとして コンパイルできる! 厳密な型定義できる! 優れたメモリ管理理(GC) 実⾏行行時最適化(JIT) デバッガ、解析ツール • 豊富なOSS • 統合開発環境の活⽤用 • 優れた静的解析ツール • • • • •
やったことその2 テストできないとは? テストできる アーキテクチャに • • • • 根本的な原因として、プロダクトが 「モノリシック」なカタマリになっ ているためと仮定。 ユニットテストしたい単位で単独で 動かせない状態。 動かす準備が⼤大変 ブラックボックステストになる CI/CDの中で実⾏行行できない etc… ただ、現⾏行行システムで既にサーバー 分割とAPI化によって準マイクロ サービス的な構成となっていた。 おかげで考慮対象はサーバー上で動 くアプリケーションコードのみで済 んだ。
モノリシックなコードの例例 P 16 これらは⼀一⾒見見するとA, B, Cというクラスに分 割されているように⾒見見えるが・・・ public class A { public void x() { B b = new B(); b.y(); } } public class B { public void y() { C c = new C(); c.z(); } } public class C { public void z() { DB操作とか } }
これにテストケースを仕掛けてみると・・・ P Class AにテストケースつくるとB, Cも必ず くっついて来て単独テストにならない。 public class A { public void x() { B b = new B(); b.y(); } } TestCaseA (A, B, Cの複合テスト) public class B { public void y() { C c = new C(); c.z(); } } TestCaseB (BとCの複合テスト) public class C { public void z() { DB操作とか } } TestCaseC (これはまだ単独) 17
ポイントは、 「呼び出し先の実装クラスを⾃自分で newしていること」 これだとクラス同⼠士が密結合する。 このようなことから、 「テストできるような構造じゃない からやらない」 という結論論となり、放置される。 この状態で無理理にテストケースを仕 掛けたとしても、 • 実⾏行行条件が増える • バリエーションが掛け算で増加 • メンテ、問題箇所の特定が困難 これを解決するために全⾯面的に採⽤用 したアーキテクチャが 「Dependency Injection」
DI(JavaでSpringFramworkの場合) P 19 実装クラスとインタフェースを分離離、コール先のインタフェースのみ認識識 し、実装インスタンス(Dependency)は外部から注⼊入(Injection)。 public class Aimpl implements A { @Autowired private B b; public class Bimpl implments B { @Autowired private C c; public void x() { b.y(); } public void y() { c.z(); } } } public class Cimpl implements C { public void z() { DB操作とか } }
これにテストケースを仕掛けてみると・・・ P 20 今まではA単独のテストができなかったが、依存するBをモック化するこ とでテストしたい処理理だけに対して確認を⾏行行えるようになる。 class TestCaseA { def testA() { def a = new Aimpl() //モック注入 a.b = Mock(B.class) //テスト実行、assert a.x() } } public class Aimpl implements A { @Autowired private B b; public void x() { b.y(); } }
これにより、依存する他モジュール をnewしなくなる。 ただし、絶対にあらゆるクラスの newが禁⽌止というわけではなく、 「テストの都合で分離離しててほしい 単位」 でこれが適⽤用されていればよい。 そうなると、テスト時にモックを⾃自 由に差し込めるようになるので単独 テストが可能となる。 利利⽤用してるDIコンテナはOSSの 「SpringFramework」
やったことその3 テスト向けDSL採⽤用 (Spock) JUnitはJava⾔言語でテストを書くこ とになるが、Java⾔言語はテストを 表現する⽂文法を持たない。 またJMockはかなり変態コードにな るため更更にきつい。 当初使おうとしていたのは定番のこ のあたり。 • JUnit • JMock ただ・・・ テストケースの作成に労⼒力力が掛かり 過ぎるようだと、やはり 「テスト書くのがきつすぎるのでや らない」 ということになる。
そこで選んだのが Spock 主な機能としては • • • • テスト実⾏行行 BDD的なテスト記述⽂文法 柔軟なモック/スタブ⽣生成 テストパターンデータの記述 Junitの上に構築されたものだが、 記述⾔言語は、JVM⾔言語の 「Groovy」 その上にテストを記述するための DSL(ドメイン固有⾔言語)が構築され ている。 特に良良いのが、Groovy⾃自体がJava よりも⾊色々省省略略して書けること。 テストコードはJava⾔言語では書き たくない。
Spockによるテストコード例例 class SampleSTest extends Specification { def “データ更新テスト(#testname)”() { given: def target = new SampleImpl() target.x = Mock(X.class) when: def result = x.update(request) then: assert result == response where: testname | request | response “パターンA” | “foo1” | “bar1” “パターンB” | “foo2” | “bar2” } } P 24 テストケースで以下のようなブロックに区切って コードを書ける。 • • • • givenがテスト対象のセットアップ whenがテスト対象の実行 thenがテスト結果の確認 whereがテストデータ Mockの生成とその挙動も全てテストコードの中 で記述できるのが良いところ。 テストデータを複数件かけばその件数分で全 体をループして実行してくれる。 Groovyの型推論や省略記法も楽で助かる。
やったことその4 アーキテクチャとテストケースそれ ぞれのアプローチからテストができ ない理理由を取り除いた。 CI/CD, 静的解析 これでやっとCI/CDの中であたりま えにテストを⾏行行うようになった。 さらに⾏行行っていることその1 さらに⾏行行っていることその2 Cloverによるカバレッジ計測 Coverityによる静的解析 • Quality Advisor • Test Advisor
やったことその5 インタフェース 定義⾔言語 システム全体をマイクロサービス アーキテクチャで構成すると、バッ クエンドの各機能を 「Webサービス(API)」 として提供する。 こちらはテストとは別の観点からの 開発効率率率アップと品質向上にむけた 取り組み。 その実装とテストのためには以下を ⾏行行うことになる。 • 外部仕様(APIIF)の定義と公開 • 外部仕様どおりの実装 • 結合試験
API提供側では、 API利利⽤用側では、 ⼈人がAPIIFを考えて、それを頑 張ってドキュメントとして書く。 ⼈人がAPIIFドキュメントを読んで 理理解して、 それを読んで実装する。 それを利利⽤用する処理理を実装する。 「仕様/ドキュメント/実装」を⼈人の 脳が頑張って変換しながら何種類も成 果物つくってるが・・・。 この変換時の認識識違いによるズレは結 合試験まで発⾒見見できない。 でも・・・ IFって静的なものなんだから、宣 ⾔言的に記述できるはず。 宣⾔言的な記述ならそこからコード も⽂文書も⽣生成できる。
そこで導⼊入したのが インタフェース 定義⾔言語 以下の理理由から断念念 • バリデーションが表現できない • ドキュメントが⽣生成できない 当初はOSSの以下あたりを使えない かと検証したが、 • Googole ProtocolBuffer • Apache Thrift イチから以下を⾏行行いました。 • • • • • ⾔言語⾃自体の⽂文法設計 コンパイラ開発 Javaコードジェネレータ ドキュメントジェネレータ ドキュメント表⽰示サーバー
IDLでAPIIF設計を すると・・・ 1.APIIFをIDL⽂文法で記述 記述した内容はIDLコンパイラを通 すことで整合性チェックを⾏行行うこと が出来る。 2.ドキュメント⽣生成して公開 3.Javaコード⽣生成して利利⽤用 ドキュメントジェネレータを通して JSONデータを⽣生成し、それをドキュ メント表⽰示サーバーにアップロード して公開する。 Javaコードジェネレータを通して 実装で利利⽤用する。⽣生成コードは編集 は⼀一切切せず利利⽤用のみとして、常に上 書き更更新可能にしている。
振り返っての所感
問題検出をできるだけ 前⼯工程にもってくる システム開発は先の⼯工程(結合試 験とか総合試験とか)に進めば進 むほどソースコードが開発者の⼿手 元を離離れる。 そこで⾒見見つかった問題を修正して 環境に届けるには相応の時間がか かる。 IFを結合前に安定させられるIDL、 コンパイルと型宣⾔言、テストを書 いて⼿手元でもCI/CDの中でも動か して、最後にCoverityの静的解析。 後で発覚した問題への対応コスト はただただ増していくばかり。 開発者の⼿手元を離離れる直前までや れることをやる。
テストできないと⾔言い 訳される要因を潰す テストに向かないアーキテクチャ や道具を使うと対応コストを理理由 にやらないことが正当化されやす い。 アプリケーションアーキテクチャ まで踏み込んでテストを考慮する ことができればベスト。 テストできない理理由が消えると、エ ンジニアはわりとちゃんとテスト書 くようになる。 ただこれが出来るのはかなり幸運 なこと。 書いたほうがいいのは皆わかってる し、書いたものは皆動かしたい。
未熟でも早め適⽤用/ フィードバック受けて 磨く 机上で考えるよりも実践の場で揉 まれるほうが⼀一番早い。 やり⽅方を⼤大幅に変えた時は最初から いろいろ頑張りたくなる。 でもそこは⼀一旦最低限に押さえて早 く実戦投⼊入することが⼤大事。 ただし最初の適⽤用プロダクトでは 途中で⾊色々と⽅方針変更更が⼊入りがち。 それを承知してもらうのと、でき ればしがらみの無い新規プロダク トがベスト。
部⾨門⻑⾧長サポートと 仲間が⼤大事 部⾨門⻑⾧長や組織が • • 攻めの開発(ビジネス要件対応) 守りの開発(保守/刷新) の両⽅方を理理解してくれて初めて効果的 に取り組める。 現場で「こうしたい」という思いが あっても、仕事で開発をしている以 上はビジネス要件への対応は⼀一番⼒力力 を割くべきところ。 個⼈人の能⼒力力とアウトプットはかなり 限られる。 仲間がいれば、個の範囲を越えた成 果が必ず出る。