https://d226lax1qjow5r.cloudfront.net/blog/blogposts/improve-your-software-project-part-two-making-changes/part-two.png

ソフトウェア・プロジェクトの改善 - パート2:変更を加える

最終更新日 November 28, 2022

所要時間:1 分

コードベースを引き継いだとき、コードの書き方や構成に不満があることに気づいたことはないだろうか?よくある話だが、多くの頭痛の種を引き起こす可能性がある。技術的負債は雪だるま式に増えていき、コードを理解したり新しい機能を追加したりするのが飛躍的に難しくなります。

この3部構成のシリーズでは、ピカピカの(古い)プロジェクトをよりハッピーにするためにやっておきたい重要なことを紹介していく。具体的な例をいくつか挙げると、オープンソースの Vonage Python SDKVonageのAPIにHTTPコールをするライブラリだが、原理はどんな種類のソフトウェアプロジェクトにも当てはまる。

この記事の例はPythonで書かれていますが、これらの原則はどの言語のプロジェクトにも当てはまります。また 便利なチェックリストもあります。

シリーズ各セクション

  1. パート1:コードベースを理解する

  2. パート2:変更を加える(この記事です)

  3. パート3次のレベルの強化

パート2の内容は?

後編では、以下について話そう:

  • 自信を持って変更を加え、技術的負債に対処できるようになる

  • 上司、チーム、ユーザーとの信頼関係の構築

この記事を読み終える頃には、関係者を満足させながら、プロジェクトの状況をより良いものに変える準備が整っていることだろう。

始めよう!

構造の固定

もしあなたが パート1また、コードベース(と、運よくテストがあればそのテスト)がどのような構造になっているのか、そしてその構造がどのような目的に役立っているのかもわかっていることでしょう。今こそ、全体的なアーキテクチャについて考え、プロジェクトをより論理的に再構築する絶好の機会です。

具体例

例として使っているプロジェクトは Vonage Python SDKで、ユーザが多くの異なるVonage APIを呼び出すことを可能にする。私の場合、プロジェクトのマクロ構造は以下のようになっています:

Image of the original structure of the Vonage Python SDK

上の図から、コードが6つの主要なモジュール(単一ファイル、そうPythonはとてもコンパクトなのだ!)に分割されているのがわかるだろう:

  • モジュール __init__モジュールは、通常、コードをパッケージ化するための最小限のファイルである。

  • 内部メソッド _internal内部メソッドを含むモジュール(そして、なぜか我々がサポートするAPIの一つでもある)。

  • sms, voiceそして verifyモジュールがあり、それぞれに単一のAPIを呼び出すコードが含まれている。

  • を含む errorsモジュールには、すべてのカスタムエラー

これを見たときの最初の疑問は、他のAPIはどこにあるのだろうということでした。私がVonageに入社したとき、SDKは12種類のAPIをサポートしていましたが(その後、さらに追加しました!)、これらのAPIに関連するモジュールは3つしかありませんでした。

その結果、コードの約90%が実際に __init__.py多くのAPIの実装、非推奨のメソッド、JWT生成のようなタスクのロジック、ベースとなるAPIリクエスト、その他のオプション設定......。私はこれをリファクタリングすることにした。 Clientクラスを作り、APIにちなんだ名前のクラスを作ることでリファクタリングすることにした。

結局、こんな構造になった:

The structure of the source code, split up into the function each module serves

自分でやる

プロジェクトの構成を見てください。自分自身に問いかけてみよう。似たような機能を、理解しやすい程度に小さなモジュールにまとめる。

また、似たようなメソッドを保持するクラスを作成し、各クラスにカスタム・エラーを設定して、物事が壊れたときに何が問題なのかを自分自身やユーザーにもっと分かりやすく伝えることをお勧めする。

新しいプロジェクトを始めるとき、おそらくそれは「完成」した状態にはなっていないでしょう。ソフトウェアプロジェクトは常に進行中であり、絶え間なく繰り返される改善が、バージョン管理を使う大きな理由です。このような状況では、バージョン管理システムの中に、機能、拡張、バグ修正を含むブランチがあることに気づくかもしれません。

多くの場合、理想的な状況は次のようなものだ。1つのメインブランチで機能を開発し、セカンダリ機能ブランチで開発した機能をメインブランチにマージする。

Main branch with branches created from it to deliver features - a "good" case

プロジェクトによっては、メインコードベースとは別にテストしたいベータ機能がある場合、より長いベータブランチを持つことは理にかなっているかもしれません。Vonage Python SDK では、現在 ベータブランチがあり、これは Vonage Video API を呼び出すコードです。このブランチが呼び出す API は公式にはリリースされていないので、このブランチをマージしたくありません。この場合、ブランチの構造は以下のようになります:

Main branch and a long-lived beta branch, both of which have features developed and merged.

機能を別々に開発しなければならない場合、定期的にベータブランチをリベースして、メインブランチからの変更を取り込むのが賢明かもしれません - これは私がVonage Python SDKでやっていることです。

それが "良い "ケースだ。しかし、あるプロジェクトが始まると、次のようになるかもしれない:

A branch structure that needs to be simplified

つまり、ブランチが多すぎて一貫性がないのだ。通常、複数の開発者が多くの機能を同時に開発していた場合、このような事態に陥ることが多いが、以前のエンジニアが新しいブランチを使ってさまざまな作業を計画したり、新しいことを試したりしていたにもかかわらず、それを完成させることができなかったという場合もある。

このような場合、これらのブランチは、前のプロジェクトオーナーがどのようにプロジェクトを扱っていたかを知るための大きなヒントになります。しかし、余分なブランチの内容を理解したら、通常はそのブランチを閉じるのがよいでしょう。目的は、自分のアイデアを実装するために、できるだけ白紙の状態を作ることです。

チェスタトンのフェンス - 取り外すものを理解する!

チェスタトンの『フェンス』について、(私も含めて)みんなに思い出してもらういい機会だと思う。 チェスタトンの柵- という格言をみんなに思い出させるいい機会だと思う。ブランチやコードの一部を削除する前に、その目的を理解する努力をしよう!最も重要なことは、なぜそこにあるのかわからないからといって、何かを削除してはいけないということだ。

正しい依存関係の選択

ほとんどすべてのソフトウェア・プロジェクトは、他人のコードに依存している。オープンソースの素晴らしさは、あなたのコードが登場する前に、多くの問題や課題が解決されていることだ。しかし、あなたのコードが持っている依存関係や、それらが何に使われているのか、その仕事にとって最適なツールなのかを理解することは重要だ。

過去には適していた依存関係も、時間の経過とともに適さなくなる可能性がある:

良い依存関係とは何か?

依存関係を維持する(または置き換える)ことを選択する場合、このセクションは、依存関係が使用する価値があるかどうかを評価するのに役立つはずです。

まず、ライセンスを考慮しよう。あなたのプロジェクトでその依存関係を使うことが許可されていることを確認してください。許可されていなければ、それはノーだ。何を言っているのかよくわからないという人は、Snykのこの記事を読んでほしい、 Snykのこの記事の記事を参照してください。

次に、その依存関係が活発にメンテナンスされているか?オープンソース・プロジェクトのコミット履歴を見ることができる。あるものはとてもシンプルで、ほとんどメンテナンスの必要がない。より複雑なプロジェクトは、積極的にメンテナンスされるべきです。つまり、頻繁にコミットが行われ、問題やPRが長い間無視されることはありません。依存関係が新しい言語バージョンをサポートし、新しい機能や使用している他の依存関係とうまく機能するようにしたいので、これは重要なことです。

依存関係の人気度を考慮する価値はあります。すべての依存関係にセキュリティの脆弱性がある可能性がありますが、より広く使われている依存関係ほど、自動化ツールやセキュリティ研究者、他のユーザーによって、これらの脆弱性にフラグが立てられる可能性が高くなります。また、広く使われているオプションを選ぶことは、あなたが行き詰まったときに、より多くの人が質問に答えることができることを意味します!

最後に、サポートする資産を検討してください。この依存関係を使用する信頼できるサンプルがオンラインにありますか?こういったことは、作業を始めたり問題を素早くデバッグしたりするのに役立つので、検討する価値がある。

完璧な依存関係のセットを見つけたということか?いいニュースだ...今のところは。メンテナンス担当者がプロジェクトを放棄してしまったり、あなたの要件が変化して、そのライブラリが適さなくなったりする可能性があるからです。ここで私ができる最善のアドバイスは、時間をかけて依存関係を検証し続けることだ!それらがまだあなたのニーズに合っていることを確認してください。将来、あなたのニーズにさらにぴったり合う新しいライブラリが登場するかもしれません。

所有していないコードのテスト

プロジェクトが他のコードと通信する必要がある場合、コードの機能をどのようにテストするかという茨の道が現れます。この例では、Vonage Python SDKの主なタスクは多くの異なるAPIを呼び出すことです。私たちはAPIそのものや、APIがどのように振る舞うかをコントロールすることはできません。

この例としては、SDKを使用して以下を呼び出す場合があります。 Vonage の SMS API を呼び出す場合です。.ここでは、あなたの正確なニーズによって様々なことが起こり得ますが、具体的な例を挙げます。Python SDKを使ってSMSを送信するのはとても簡単なプロセスです:

import vonage

client = vonage.Client(key="API_KEY", secret="API_SECRET")
client.sms.send_message(
    {
        "from": "SENDER_NAME"
        "to": "RECIPIENT_PHONE_NUMBER",
        "text": "A text message sent using the Vonage SMS API",
    }
)

しかし、このコードを実行すると、いろいろなことが起こる。以下は(非常に)簡略化したバージョンである:

  1. SDKはVonageサーバーにリクエストを送信します。

  2. サーバーはリクエストを解析し、レスポンスをSDKに送り返す。

  3. リクエストに問題がなければ、サーバーは受信者の電話番号にSMSを送信する。

  4. ステータス情報(配達受領など)がVonageサーバーに返送されます。

  5. オプションとして、この情報はVonageサーバーからユーザーが指定したウェブフックに送信され、SMSが正常に配信されたかどうかなどの情報を見ることができます。

The (simplified) chain of events that happen when you want to send an SMS with our codebase

このような状況では、私たちのコードを実行すると、他の多くのコードが実行されることになります。SDKからこの他のコードをテストすることはできないので、テストを書くときには、正しい情報を送れば正しいことが起こり、正しいレスポンスが返ってくるという仮定をしなければならない。

この例では、テストできるのはこの部分だけだ:

The only part of the above workflow we can test

つまり、我々のコードが評価されるのは、次のような質問だけだ:1.送信するリクエストは整形式か、2.レスポンスは適切に処理されているか。

まとめると、テストの範囲は、あなたのコードがどのようにデータを送受信するかだけをテストし、他のソフトウェアが正しく仕事をし、あなたのコードが処理できるように成功とエラーのレスポンスを送信すると仮定する。テストの際には、APIリクエストをキャプチャし、モックしたレスポンスをリクエストに応じた正しい形で返すことを検討しましょう。そうすることで、テストを実行する際に、コントロールできない外部サービスを実際に呼び出すことなく、コードがこのモックデータを消費して処理できるようになります。

テストは、あなたのコードがテストスイートの仕様に準拠していることを証明するものであり、実際のAPIの動作に準拠しているとは限らないことを忘れないでください。新しい機能を追加する際には、テストを実行するだけでなく、実際にコードを使用してリクエストを行うデモアプリを作成するのがよいでしょう (この方法は、将来テストで使用するためにキャッシュするためのすばらしいレスポンスも提供します!)。

最初のリリースを作る

コードベースを整理し、再構築を行い、テストを改善し、そしておそらくプロジェクトに新しい機能を追加しました。新しいリリースを作る時が来た!

このプロセスは、ユーザーとチームにあなたが何をしているかを知ってもらうという信頼を築くためのものです。その目的は、ユーザーがあなたの変更を信頼できること、そしてアップデートが彼らの知らないところで彼らのワークフローの一部を壊すことはないことを示すことです。ここで重要なのは透明性です。ユーザーに隠れた驚きを与えたくありません。

セマンティック・バージョニングの使用

最も重要な推奨事項は、次のとおりである。 セマンティック・バージョニング!セマンティック・バージョニングでは、バージョン番号をx.y.zの構造で表します:

  • xはメジャーバージョンで、変更を加える際に上げる必要がある、

  • y はマイナーバージョンで、後方互換機能を追加するときに上げる必要があります。

  • zはパッチのバージョンで、バグフィックスや依存関係の更新などを行う際に、後方互換性を保つために上げる必要がある。

実際の例として Vonage Messages API(のサポートを追加したとき(ただし、新しいリリースが後方互換性を失うような変更はしていない)、私はマイナーリリースを作成した。 v2.7.0 -> v2.8.0.非推奨のメソッドをいくつか削除し、SDKでオブジェクトをインスタンス化する方法を変更したいと思ったとき、既存の機能が壊れてしまうことがわかっていたので、メジャーリリースを作りました、 v2.8.0 -> v3.0.0.新しいコードにバグを見つけたときは、それを修正し、パッチリリースでSDKを更新しました、 v3.0.0 -> v3.0.1.これらのルールに従うことで、ユーザーが最新バージョンにアップグレードする際に、何を期待すればよいかを正確に伝えることができます。もしメジャーリリースをすれば、ユーザは、大きな変更を期待することができます!

補足資料を更新

新しいリリースをするときはいつでも、変更履歴を更新しましょう。通常、新バージョンのすべての違いが明記されているため、ユーザーが最初に見る場所であることが多く、アップデートすべきかどうかを判断するのに役立ちます。もし変更履歴を更新しなければ、ユーザーは新しいバージョンに何を期待すればいいのかわからず、結果的に警戒心を強めるでしょう。

同様に、新しいリリースを作成するときは、ドキュメント、README、コードサンプルを最新のバージョンに保つようにしましょう。もしそうしなければ、ユーザーは必ずしも素晴らしい新機能の使い方を知っているとは限りませんし、もし変更を加えた場合、ドキュメントやコードサンプルは完全に機能しなくなってしまうかもしれません。

プロジェクトの古くからのユーザーは、自分たちが信頼しているコードが安全な手に委ねられていると信頼でき、新しいユーザーはあなたのソフトウェアの使い方を学ぶことができる。

非推奨の緩和

コードを変更し始めると、おそらく多くのコードをリストラクチャリング/リファクタリングしたくなるでしょう。コードのどれかがユーザーによって呼び出される方法を変更しない限り、これは思う存分行うことができる。しかし、何かを呼び出す方法を変更したい場合は、マイナーリリースで古いメソッドを非推奨にして新しいメソッドを追加し、後のメジャーリリースで古いメソッドを削除する必要があります。

具体的な例を挙げよう。Vonage Python SDKで、私は get_standard_number_insightメソッドの呼び出し方を変更したいと思いました。元々、これは Clientクラスに関連付けられたメソッドでした。このメソッドを含む NumberInsightクラスがインスタンス化され Clientクラスがインスタンス化して使用する。これによって Numbers Insight APIを呼び出す方法を、ユーザーがSMSメッセージを送信したり音声通話をしたりする方法と同じにすることができる。

The method I wanted to move from the Client class

まず NumberInsightクラスを作り get_standard_number_insightメソッドを追加した。

The new version of the method inside the new NumberInsight class

次に、メソッドを非推奨にした。 Clientクラスのメソッドを非推奨にしました。Pythonでは、メソッドを使うときに非推奨の警告を表示するデコレーターを追加することでこれを行うことができます。

Deprecating the method I wanted to move from this class, and the warning it prints

この後、コード・スニペットとドキュメントを更新し、変更を反映させた。

Updated docs to show the new way to call the get_standard_number_insight method

これでマイナーリリースを行うことができた。機能を非推奨にしたリリースの後は、非推奨の部分をしばらく放置してから削除するのが良い習慣だ。私は、非推奨の機能を少なくとも1リリース、もしくは1ヶ月間(どちらか長い方)残しておくことをお勧めします。繰り返しますが、これは信頼を築くためです。

私はこの古いコードを数ヶ月間放置した後、メジャーリリースを行い、これらのメソッドを呼び出す古い方法を削除した。私の意図が何であったかを几帳面に、かつ透明性をもって説明することで、ユーザーは最新バージョンにアップデートする際に何を期待すればよいかを知ることができた。

Changelog describing the major release that removed old deprecated methods, including the old version of get_standard_number_insight

改善と新規作業のバランス

レガシーコードベースに取り組み始めるときに考慮すべき最後のことは、新しい機能を追加したりバグを修正したりする前に、コードを自分の好きなように磨き上げる時間が無限にあるわけではないということだ。古いコードに大きな変更を加えようとするのと同時に、新しい機能を追加するための締め切りなど、他の優先事項をこなさなければならないでしょう。

この場合、あなたは自分自身と自分の仕事を主張しやすくなる必要がある。あなたの時間の一部は、レガシーコードベースの改善に費やされる必要があり、上司が望むような新機能の開発が迅速にできないことを想定しておく。今、リファクタリングに費やす時間は、コードベースを理解するのに役立ち、後々、メンテナンスがずっと簡単になるため、配当として返ってくることを強調する。

最終的に、レガシープロジェクトでの作業は、ユーザーエクスペリエンスと長期的な使いやすさ、メンテナンスのしやすさを向上させるはずだ。技術的負債に対処することは、ユーザーやチームにとってすぐに目に見える作業ではないため、すぐに賞賛されることはないかもしれないが、技術的負債を管理することで、後々すべての人にとって状況が悪化するのを防ぐことができる!

このプロセスで大いに役立ったのは、将来の効率化のために投資していることを示すために、発見と学習のための作業チケットを作成することだった。また、技術的負債に関するチケットも作成し、新機能の開発チケットと並行して、私が行っている作業についてチームに理解してもらうようにした。このアプローチは社内の信頼を築き、私がどのように時間を使っているかを理解してもらうのに役立ちました。

次はどうする?

この記事の提案に従ったなら、あなたのプロジェクトはすでにずっと良くなっているはずだ!あなたが作るリリースは、あなたがプロジェクトでやりたい素晴らしいことのための土台を作ることになる。

シリーズのパート3を読むにはここをクリックここでは、あなたのプロジェクトを次のレベルに引き上げる方法について説明します。ユーザーのためになる機能強化や、コードベースをより扱いやすくする方法について説明します。最後に、プロジェクトを引き継ぐ際のベストプラクティスについて説明します。

ご質問やご意見がおありですか?私たちの VonageコミュニティSlackまたは ツイッター.

シェア:

https://a.storyblok.com/f/270183/400x400/92109caf6a/max-kahan.png
Max KahanVonage 元チームメンバー

マックスはPythonデベロッパー・アドボケイトであり、通信API、機械学習、デベロッパー・エクスペリエンス、ダンスに興味を持つソフトウェア・エンジニアだ!物理学を専攻していたが、現在はオープンソースのプロジェクトに携わり、開発者の生活をより良くするためのものを作っている。