私のチームは、リファクタリング後の頻繁なエラーをどのように回避できますか?


20

少し背景を説明すると、私は約12人のRuby on Rails開発者(+/-インターン)を抱える会社で働いています。リモートワークが一般的です。当社の製品は2つの部分で構成されています。かなり太いコア部分と、それに基づいて構築された大規模な顧客プロジェクトまであります。通常、顧客プロジェクトはコアを拡張します。重要な機能の上書きは発生しません。コアには、リファクタリングを急ぐ必要のあるかなり悪い部分があると付け加えます。仕様はありますが、主に顧客プロジェクト向けです。コアの最悪の部分はテストされていません(そうではありません...)。

開発者は2つのチームに分かれており、スプリントごとに1つまたは2つのPOを使用しています。通常、1つの顧客プロジェクトは、チームとPOのいずれかに厳密に関連付けられます。

ここで私たちの問題:むしろ頻繁に、お互いのことを壊します。チームAの誰かがコア機能Yを拡張またはリファクタリングすると、チームBの顧客プロジェクトの1つに予期しないエラーが発生します。ほとんどの場合、変更はチーム間で発表されないため、ほとんどの場合、予期しないバグが発生します。POを含むチームBは、機能Yが安定していると考え、リリース前に変更を認識せずにテストしませんでした。

これらの問題を取り除く方法は?どのような「発表テクニック」を勧められますか?


34
明白な答えはTDDです。
ムービシエル14年

1
「重要な機能の上書きは起こらない」と言ったのに、どうしてそれ起こるのかという問題がありますか?チームで「コア」と「主要な機能」を区別していますか?状況を理解しようとしています...
logc 14年

4
@mouvciel 動的型付けは使用しませんが、この場合、特定のアドバイスは少し遅すぎます。
ドーバル14年

3
OCamlのような強く型付けされた言語を使用します。
ガイウス14年

@logcはっきりしないかもしれませんが、ごめんなさい。フィルターライブラリ自体のようなコア機能は上書きしませんが、顧客プロジェクトで使用するクラスに新しいフィルターを追加します。一般的なシナリオの1つとして、フィルターライブラリの変更により、顧客プロジェクトに追加されたフィルターが破壊されることがあります。
SDD64 14年

回答:


24

Michael C. FeathersによるLegacy Codeの効果的な使用を読むことをお勧めします。自動化されたテストが本当に必要である、それを簡単に追加する方法、まだ持っていない場合、そしてどのような方法でリファクタリングするための「コードの匂い」を説明しています。

それに加えて、あなたの状況におけるもう一つの中心的な問題は、2つのチーム間のコミュニケーションの欠如のようです。これらのチームの大きさは?彼らは異なるバックログに取り組んでいますか?

アーキテクチャに応じてチームを分割することは、ほとんど常に悪い習慣です。たとえば、コアチームと非コアチーム。代わりに、機能ドメインでクロスコンポーネントのチームを作成します。


「神話の男月」では、コード構造は通常チーム/組織構造に従うことを読みました。したがって、これは実際には「悪い習慣」ではなく、物事が通常行われる方法です。
マルセル14年

ソフトウェア開発のダイナミクス」で、Visual C ++の背後にいるマネージャーは、機能チームを鮮やかに持つことを推奨しています。...私は、「人月の神話」、@Marcelを読んでいないが、私の知る限りでは、それは業界の悪いプラクティスを示しています
logc

マルセル、これが物事が通常行ったり来たりした方法であることは事実ですが、機能チームなど、ますます多くのチームがそれを行っています。コンポーネントベースのチームがあると、クロスコンポーネント機能で作業するときにコミュニケーションが取れなくなります。それに加えて、ほとんどの場合、優れたアーキテクチャの目的ではなく、他のチーム/コンポーネントに責任を課そうとする人々に基づいて、アーキテクチャに関する議論が行われます。したがって、この質問の著者によって説明された状況が得られます。mountaingoatsoftware.com/blog/the-benefits-of-feature-teamsも参照してください。
トンマイスター14年

まあ、私がOPを理解している限り、彼はチームがコアチームと非コアチームに分割されていないと述べました。チームは「顧客ごと」に分割されます。これは基本的に「機能ドメインごと」です。そして、それは問題の一部です。すべてのチームが共通のコアを変更することが許可されているため、一方のチームからの変更が他方に影響します。
ドックブラウン14年

@DocBrownそのとおりです。各チームはコアを変更できます。もちろん、これらの変更は各プロジェクトにとって有益であると想定されています。ただし、それらは異なるバックログで動作します。各顧客に1つ、コアに1つあります。
SDD64 14年

41

コアの最悪の部分はテストされていません(...)。

これが問題です。効率的なリファクタリングは、一連の自動テストに大きく依存しています。あなたがそれらを持っていない場合、あなたが説明している問題が表示され始めます。これは、Rubyのような動的言語を使用する場合に特に重要です。Rubyには、メソッドにパラメーターを渡すことに関連する基本的なエラーをキャッチするコンパイラーがありません。


10
それと赤ちゃんのステップでリファクタリングし、非常に頻繁にコミットします。
ステファンビリエット14年

1
おそらくここにアドバイスを追加できるアドバイスがたくさんありますが、それはすべてこのポイントに到達します。OPの「あるべき姿」についての冗談は、それ自体が問題であることを示していますが、スクリプト化されたテストがリファクタリングに与える影響は計り知れません。パスが失敗になった場合、リファクタリングは機能しません。すべてのパスがパスのままである場合、リファクタリングは機能している可能性あります(パスに失敗すると明らかにプラスになりますが、すべてのパスをパスとして保持することはネットゲインよりも重要です.1つのテストを中断して5つの修正を行う変更は、改善はなく、リファクタリング)
ジョンハンナ

「+1」を付けましたが、これを解決するためのアプローチは「自動テスト」だけではないと思います。より良いマニュアルですが、体系的なQAで、おそらく別のQAチームが品質の問題を解決できます(おそらく、自動テスト手動テストの両方を使用するのが理にかなっています)。
ドックブラウン14年

良い点ですが、コアプロジェクトと顧客プロジェクトが別個のモジュール(さらにRubyのような動的言語)である場合、コアはテストとそれに関連する実装の両方を変更でき、独自のテストに失敗することなく依存モジュールを破壊できます。
logc 14年

他の人がコメントしたように。TDD。おそらく、可能な限り多くのコードに対して単体テストを行う必要があることすでに認識しているでしょう。単体テストを記述するためだけにリソースを浪費する一方で、コンポーネントのリファクタリングを開始するときは、コアコードに触れる前に大規模なテスト作成を開始する必要があります。
jb510 14年

5

ユニットテストを改善するための以前の回答は良いのですが、アドレス指定にはもっと根本的な問題があるかもしれません。顧客プロジェクトのコードからコアコードにアクセスするには、明確なインターフェイスが必要です。この方法により、インターフェイスを介して観察される動作を変更せずにコアコードをリファクタリングしても、他のチームのコードは壊れません。これにより、「安全に」リファクタリングできるものと、必要に応じて(おそらくインターフェースを壊す)再設計が必要なものを簡単に知ることができます。


スポットオン。より自動化されたテストは利点のみをもたらし、実行するだけの価値はありますが、ここではコアの変更を伝えることができないというコアの問題を解決しません。重要な機能をインターフェイスでラップすることによる分離は、大幅な改善になります。
ボブツウェイ

5

他の回答では重要なポイント(より多くの単体テスト、機能チーム、コアコンポーネントへのクリーンなインターフェイス)が強調されていますが、欠落していると思うポイントが1つあります。それはバージョン管理です。

リリース1を実行してコアの動作をフリーズし、そのリリースをプライベートアーティファクト管理システム2に入れると、顧客プロジェクトはコアバージョンXへの依存関係を宣言でき、次のリリースXで壊れることはありません+ 1

「アナウンスメントポリシー」は、各リリースとともにCHANGESファイルを使用するか、新しいコアリリースごとにすべての機能をアナウンスするチーム会議を開催することになります。

また、「コア」とは何か、そしてその「キー」となるサブセットをより適切に定義する必要があると思います。「正しく」「重要なコンポーネント」に多くの変更を加えることは避けているように見えますが、「コア」には頻繁に変更を加えることができます。何かに依存するためには、それを安定させる必要があります。何かが安定していない場合、コアと呼ばないでください。「ヘルパー」コンポーネントと呼ぶことを提案できますか?

編集セマンティックバージョニングシステムの規則に従う場合、コアのAPIの互換性のない変更は、メジャーバージョンの変更でマークする必要があります。つまり、新しいものを追加するだけでなく、以前に存在していたコアの動作を変更したり、何かを削除したりします。この規則により、開発者はバージョン「1.1」から「1.2」への更新は安全であるが、「1.X」から「2.0」への更新は危険であり、慎重に検討する必要があることを知っています。

1:Rubyの世界では、これはgemと呼ばれると思う
2:JavaのNexusまたはPythonのPyPIと同等


「バージョン管理」は確かに重要ですが、リリース前にコアをフリーズすることで説明した問題を解決しようとすると、簡単に洗練された分岐とマージが必要になります。理由は、チームAの「リリースビルド」フェーズ中に、Aはコアを変更する必要があります(少なくともバグ修正のため)が、他のチームからコアへの変更を受け入れないためです。チームごとのコア。「後で」マージされますが、これは技術的負債の一種です。それは時々大丈夫ですが、しばしばそれは説明された問題を後の時点に延期するだけです。
Doc Brown 14年

@DocBrown:私はあなたに同意しますが、私はすべての開発者が協力的で大人であるという仮定の下で書きました。これはあなたが説明しことを見ていないと言うことではありません。しかし、システムを信頼できるものにするための重要な部分は、まあ、安定性に努めることです。さらに、チームAがコアのXを変更する必要があり、チームBがコアのXを変更する必要がある場合、Xはコアに属していない可能性があります。それがもう一つのポイントだと思います。:)
logc 14年

@DocBrownはい、各顧客プロジェクトにコアの1つのブランチを使用することを学びました。これにより、いくつかの他の問題が発生しました。たとえば、すでに展開されている顧客システムを「タッチ」するのは好きではありません。その結果、各展開後に使用済みコアのマイナーバージョンジャンプがいくつか発生する可能性があります。
SDD64 14年

@ SDD64:それはまさに私が言っていることです-共通のコアにすぐに変更を統合しないことも長期的には解決策ではありません。必要なのは、コアのより良いテスト戦略であり、自動テストと手動テストも同様です。
Doc Brown

1
記録のために、私は各チームに別個のコアを提唱することも、テストが必要であることを否定することもしませんが、コアテストとその実装は、以前にコメントしたように同時に変更できます。リリースストリングまたはコミットタグでマークされたフリーズコアのみが、その上にビルドするプロジェクトで信頼できます(バグ修正を除き、バージョン管理ポリシーが適切であれば)。
logc

3

他の人が言ったように、優れた単体テストは問題を解決しません。各チームのテストスイートに合格しても、変更のマージ中に問題が発生します。

TDDについても同じです。これをどのように解決できるかわかりません。

ソリューションは技術的ではありません。「コア」境界を明確に定義し、「dev」または「アーキテクト」の誰かに「ウォッチドッグ」ロールを割り当てる必要があります。コアへの変更はすべて、このウォッチドッグを通過する必要があります。彼は、すべてのチームからのすべての出力が、過度の付随的損害なしにマージされることを確認する責任があります。


彼はコアのほとんどを書いたので、「ウォッチドッグ」がありました。悲しいことに、彼はテストされていない部分のほとんどを担当していました。彼はYAGNIのなりすましで、半年前に他の2人の男に置き換えられました。私たちはまだこれらの「暗い部分」をリファクタリングするのに苦労しています。
SDD64 14年

2
アイデアは、ユニットテストスイート持つことであるコアのある、コアの一部すべてのチームではなく、各チームのために別々のテストスイートからの貢献をし、。
Doc Brown 14年

2
@ SDD64:「あなたはそれを(まだ)必要としません」(これは非常に良いことです)と「あなたはコードをクリーンアップする必要はありません(まだ)」-これは非常に悪い習慣です、そして私見は全く反対です。
Doc Brown 14年

ウォッチドッグソリューションは、本当に、本当に最適ではありません、私見です。それは、システムに単一障害点を構築するようなもので、その上に人と政治が関係するため、非常に遅いものです。それ以外の場合、TDDはもちろんこの問題に役立ちます。各コアテストは、現在のコアがどのように使用されるかを顧客プロジェクト開発者に示す例です。しかし、私は...あなたが誠意を持ってあなたの答えを与えたと思う
logc

@DocBrown:わかりました、多分私たちの理解は異なります。彼が書いたコア機能は、最も奇妙な可能性を満足させるために非常に複雑です。それらのほとんど、私たちは出会ったことがありません。複雑なため、リファクタリングが遅くなります。
SDD64 14年

2

より長期的な修正として、チーム間でより適切でタイムリーなコミュニケーションも必要です。たとえば、コア機能Yを利用する各チームは、機能の計画テストケースの構築に関与する必要があります。この計画自体は、2つのチーム間の機能Yに固有のさまざまなユースケースを強調しています。機能がどのように機能するかが明らかになり、テストケースが実装され、合意されると、実装スキームに追加の変更が必要になります。機能をリリースしようとしているチームではなく、機能をリリースするチームがテストケースを実行するために必要です。衝突が発生するはずのタスクがある場合は、どちらかのチームから新しいテストケースを追加します。チームメンバーが、テストされていない機能の新しい側面について考えた場合、独自のサンドボックスに合格したことを確認したテストケースを自由に追加する必要があります。この方法では、発生する唯一の衝突はインテントレベルになり、リファクタリングされた機能が野生にリリースされる前に特定する必要があります。


2

すべてのシステムには効果的なテストスイート(特に自動化を意味します)が必要ですが、これらのテストが効果的に使用されると、これらの競合は現在よりも早くキャッ​​チされますが、根本的な問題には対処しません。

この質問は、少なくとも2つの根本的な問題を明らかにします。個々の顧客の要件を満たすために「コア」を変更する方法と、チームが変更を行う意思を伝えて調整することの失敗です。これらはどちらも根本原因ではないため、修正する前にこれがなぜ行われているのかを理解する必要があります。

最初に決定すべきことの1つは、開発者とマネージャーの両方がここに問題があることを認識しているかどうかです。少なくとも一部の人がそうするなら、あなたは彼らがそれについて何もできないと思うか、そうしないことを選ぶ理由を見つける必要があります。そうでない人のために、あなたは彼らの現在の行動が将来の問題をどのように作り出すかを予測する能力を高めようとするかもしれません。物事がどのようにうまくいかないかを認識している従業員がいるまで、問題を解決することはできません(少なくとも短期的にはおそらくそうではありません)。

少なくとも最初は問題を抽象的な用語で分析するのは難しいかもしれないので、問題を引き起こした特定のインシデントに焦点を合わせ、それがどのように起こったかを判断しようとします。関係者は防御的である可能性が高いため、実際に何が起こっているのかを知るためには、利己的で事後的な正当化に注意する必要があります。

可能性が低いため、言及することをためらう可能性が1つあります。顧客の要件が非常に異なるため、共有コアコードを正当化するための共通性が不十分です。その場合、実際には複数の個別の製品があり、それらを管理し、それらの間に人為的な結合を作成する必要はありません。


製品をJavaからRoRに移行する前に、実際にあなたが提案したようにしました。私たちはすべての顧客のためにJavaコアを持っていましたが、彼らの要件はある日それを「壊し」、それを分割しなければなりませんでした。そのような状況の中で、次のような問題に直面しました。「おい、顧客Yはこのような素晴らしいコア機能を持っています。コアが互換性がないため、顧客Zに移植できないのは残念です」。Railsでは、「万人のための1つのコア」ポリシーを厳しく求めています。必要に応じて、抜本的な変更を提供しますが、それにより、それ以降の更新から顧客が切り離されます。
SDD64 14年

TDDに電話するだけでは十分ではないようです。ですから、中核的な提案の分割に加えて、あなたの答えが一番好きです。悲しいことに、コアは完全にはテストされていませんが、それですべての問題が解決されるわけではありません。コア仕様のみが顧客間で共有されるため、1人の顧客に新しいコア機能を追加することは完全に問題なく見えるかもしれません。気づかない人、すべての可能な顧客に何が起こるか。だから、私はあなたの提案が好きで、問題を見つけて、それらを引き起こした原因について話す。
SDD64 14年

1

私たちは皆、ユニットテストが進むべき道であることを知っています。しかし、これらをコアに現実的にレトロフィットすることは難しいことも知っています。

機能を拡張するときに役立つと思われる特定の手法は、既存の機能が変更されていないことを一時的かつローカルに検証することです。これは次のように実行できます。

元の擬似コード:

def someFunction
   do original stuff
   return result
end

一時的なインプレーステストコード:

def someFunctionNew
   new do stuff
   return result
end

def someFunctionOld
   do original stuff
   return result
end

def someFunction
   oldResult = someFunctionOld
   newResult = someFunctionNew
   check oldResult = newResult
   return newResult
end

存在するシステムレベルのテストを介してこのバージョンを実行します。すべてが正常であれば、破損していないことがわかっているので、古いコードを削除することができます。古い結果と新しい結果の一致を確認するとき、違いを分析するコードを追加して、バグ修正などの意図した変更により異なるはずのケースをキャプチャすることもできます。


1

「ほとんどの場合、変更はチーム間で発表されないため、ほとんどの場合、バグは予想外の影響を受け

ます」適切なコミュニケーションがあることを確認することについて(他の全員がすでに指摘していることに加えて、厳密なテストを行う必要があるということについて)人々は、彼らが書いているインターフェースが次のリリースで変更されることを知っており、それらの変更はどうなるのか?
また、開発中にできるだけ早くダミーのインターフェース(空の実装)にアクセスできるようにして、独自のコードの作成を開始できるようにします。

これらすべてがなければ、最終段階でシステムの各部の間に強打から何かが出ていることを指摘する以外は、単体テストはあまり効果がありません。あなたはそれを知りたいが、それを早く、非常に早く知り、チームがお互いに話し合い、努力を調整し、実際に他のチームが行っている仕事に頻繁にアクセスできるようにする必要がある数週間または数か月後、配達の1〜2日前にコミットします)。
あなたのバグはコードにはなく、間違いなくあなたが書いているインターフェースをいじっていることを知らなかった他のチームのコードにはありません。バグは開発プロセスにあり、人々間のコミュニケーションとコラボレーションの欠如です。別の部屋に座っているからといって、他の人から自分を隔離する必要はありません。


1

主に、コミュニケーションの問題(おそらくチーム構築の問題ともリンクしている)があるため、あなたのケースの解決策は、開発技術ではなくコミュニケーションに焦点を当てるべきだと思います。

顧客プロジェクトの開始時にコアモジュールをフリーズまたはフォークすることはできないことは当然です(そうでない場合は、コアモジュールの更新を目的とする非顧客関連プロジェクトを会社のスケジュールに統合する必要があります)。

したがって、チーム間のコミュニケーションを改善しようとする問題が残っています。これには2つの方法で対処できます。

  • 人間と。つまり、あなたの会社は、コードの品質と可用性に責任を持つコアモジュールアーキテクト(またはトップマネジメントに適した専門用語)として誰かを指定します。この人は核を転生します。したがって、彼女はすべてのチームで共有され、チーム間の適切な同期を確保します。さらに、彼女はコアモジュールにコミットされたコードのレビュー担当者としても機能し、その一貫性を維持する必要があります。
  • ツールとワークフローで。コアに継続的インテグレーションを課すことにより、コアコード自体を通信媒体にします。これには、最初に(自動テストスイートを追加することにより)ある程度の労力が必要になりますが、その後、夜間のCIレポートはコアモジュールの全体的なステータス更新になります。

コミュニケーションプロセスとしてのCIの詳細については、こちらをご覧ください

最後に、企業レベルでのチームワークの不足という問題があります。私はチームビルディングイベントの大ファンではありませんが、これはそれらが役立つケースのようです。開発者全体の会議を定期的に開催していますか?他のチームの人々をプロジェクトの回顧展に招待できますか?それとも、金曜日の夕方のビールが時々あるでしょうか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.