シンプルさのためのPythonアプリケーションのリファクタリング

シンプルさのためのPythonアプリケーションのリファクタリング

よりシンプルなPythonコードが必要ですか? 常に、最善の意図、クリーンなコードベース、素晴らしい構造でプロジェクトを開始します。 しかし、時間が経つにつれて、アプリに変更が加えられ、事態は少し面倒になります。

簡潔でシンプルなPythonコードを記述して維持できれば、長期的に多くの時間を節約できます。 コードが適切にレイアウトされ、従うのが簡単な場合、テスト、バグの発見、および変更にかかる時間を短縮できます。

このチュートリアルでは次のことを学びます:

  • Pythonコードとアプリケーションの複雑さを測定する方法

  • コードを壊さずに変更する方法

  • 余分な複雑さを引き起こすPythonコードの一般的な問題とその修正方法

このチュートリアルでは、大都市の地下鉄システムのナビゲートは複雑になる可能性があるため、地下鉄道ネットワークのテーマを使用して複雑さを説明します。 うまく設計されたものもあれば、過度に複雑に見えるものもあります。

無料ボーナス: link:[5 Thoughts On Python Mastery]、Python開発者向けの無料コースで、Pythonのスキルを次のレベルに引き上げるのに必要なロードマップと考え方を示します。

Pythonのコードの複雑さ

アプリケーションとそのコードベースの複雑さは、実行するタスクに関連しています。 NASAのジェット推進研究所(文字通りhttps://github.com/chrislgarry/Apollo-11 [ロケット科学])のコードを書いている場合、それは複雑になります。

質問はそれほど多くありません。「私のコードは複雑ですか?」 「私のコードは必要以上に複雑ですか?」

東京の鉄道網は、世界で最も広範で複雑なものの1つです。 これは、東京が3,000万人を超える大都市であることに一部起因しますが、3つのネットワークが互いに重複しているためでもあります。

都営および東京メトロの高速輸送ネットワークに加えて、東京中央部を通過するJR東日本の電車があります。 最も経験豊富な旅行者でさえ、東京の中心部をナビゲートするのは非常に複雑です。

以下は、東京鉄道網の地図です。

コードがこのマップのように見え始めている場合、これがチュートリアルです。

最初に、ミッションの相対的な進捗を測定してコードを簡素化するためのスケールを与えることができる複雑さの4つのメトリックを検証します。

Code Complexity Metrics目次、幅= 1667 、height = 522

指標を調べた後、これらの指標の計算を自動化する「+ wily +」というツールについて学習します。

複雑さを測定するためのメトリック

コンピュータソフトウェアの複雑さの分析には、多くの時間と研究が費やされています。 過度に複雑で保守不可能なアプリケーションには、非常に大きなコストがかかる可能性があります。

ソフトウェアの複雑さは品質と相関しています。 読みやすく理解しやすいコードは、今後開発者によって更新される可能性が高くなります。

プログラミング言語のいくつかの指標を以下に示します。 Pythonだけでなく、多くの言語に適用されます。

コードの行

LOC、またはコード行は、複雑さの最も粗雑な尺度です。 コード行とアプリケーションの複雑さの間に直接的な相関関係があるかどうかは議論の余地がありますが、間接的な相関関係は明らかです。 結局、5行のプログラムは、500万行のプログラムよりも単純である可能性が高いです。

Pythonメトリックスを見るとき、空白行とコメントを含む行を無視しようとします。

コード行は、LinuxおよびMac OSで「+ wc 」コマンドを使用して計算できます。「 file.py +」は測定するファイルの名前です。

$ wc -l file.py

すべての `+ .py `ファイルを再帰的に検索して、結合された行をフォルダーに追加したい場合、 ` wc `を ` find +`コマンドと結合できます。

$ find . -name \*.py | xargs wc -l

Windowsの場合、PowerShellは、 `+ Measure-Object `で単語カウントコマンドを提供し、 ` Get-ChildItem +`で再帰的なファイル検索を提供します。

$ Get-ChildItem -Path *.py -Recurse | Measure-Object –Line

応答では、行の総数が表示されます。

アプリケーションのコード量を定量化するためにコード行が使用されるのはなぜですか? 前提は、コードの行はおおよそ文に等しいということです。 行は、空白を含む文字よりも優れた尺度です。

Pythonでは、各行に1つのステートメントを配置することをお勧めします。 この例は、9行のコードです。

 1 x = 5
 2 value = input("Enter a number: ")
 3 y = int(value)
 4 if x < y:
 5     print(f"{x} is less than {y}")
 6 elif x == y:
 7     print(f"{x} is equal to {y}")
 8 else:
 9     print(f"{x} is more than {y}")

複雑さの尺度としてコード行のみを使用した場合、間違った動作を助長する可能性があります。

Pythonコードは読みやすく、理解しやすいものでなければなりません。 最後の例を使用すると、コードの行数を3行に減らすことができます。

 1 x = 5; y = int(input("Enter a number:"))
 2 equality = "is equal to" if x == y else "is less than" if x < y else "is more than"
 3 print(f"{x} {equality} {y}")

しかし、結果は読みにくく、PEP 8には最大行長と改行に関するガイドラインがあります。 PEP 8の詳細については、https://realpython.com/python-pep8/[PEP 8で美しいPythonコードを記述する方法]を参照してください。

このコードブロックは、2つのPython言語機能を使用してコードを短くします。

  • 複合ステートメント:「+; +」を使用

  • 連鎖条件文または三項文: + name = value if condition else value if condition2 else value2 +

コードの行数を減らしましたが、Pythonの基本的な法則の1つに違反しています。

_ _ 「読みやすさのカウント」

— Pythonの禅、ティムピーターズ _ _

コードのメンテナーは人間であり、この短いコードは読みにくいため、この短縮されたコードは潜在的に保守が困難です。 複雑さについて、より高度で有用ないくつかのメトリックを検討します。

巡回複雑度

循環的複雑度は、アプリケーションを介した独立コードパスの数の尺度です。 パスは、アプリケーションの最後に到達するためにインタープリターがたどることができる一連のステートメントです。

循環的な複雑さとコードパスを考える1つの方法は、コードが鉄道網のようなものだと想像することです。

旅のために、目的地に着くために列車を変える必要があるかもしれません。 ポルトガルのリスボン首都圏鉄道システムは、シンプルで簡単にナビゲートできます。 旅行の循環的な複雑さは、旅行する必要がある路線の数と同じです。

Alvalade_から_Anjos_に行く必要がある場合、_linha verde(緑の線)で5駅停車します。

この旅行の循環的複雑度は1です。これは、列車が1本しかないためです。 簡単な旅です。 この類推では、そのトレインはコードブランチに相当します。

Aeroporto(空港)からhttps://pasteisdebelem.pt/en/[_Belém_地区の料理]をサンプリングするために旅行する必要がある場合、より複雑な旅になります。 _Alameda_と_Cais doSodré_で列車を変更する必要があります。

3つの列車に乗るため、この旅行の循環的複雑度は3です。 タクシーに乗る方がいいかもしれません!

リスボンをナビゲートするのではなく、コードを記述する方法を見ると、トレインラインの変更は、実行中のブランチ(「+ if +」ステートメントなど)になります。 この例を見てみましょう。

x = 1

このコードを実行できる方法は1つしかないため、循環的複雑度は1です。

決定を追加するか、 `+ if +`ステートメントとしてコードに分岐すると、複雑さが増します。

x = 1
if x < 2:
    x += 1

このコードを実行できる方法は1つしかありませんが、「+ x 」は定数であるため、循環的複雑度は2です。 すべての循環的複雑度アナライザーは、 ` if +`ステートメントをブランチとして扱います。

これは、非常に複雑なコードの例でもあります。 `+ x `には固定値があるため、 ` if +`ステートメントは役に立ちません。 この例を次のように単純にリファクタリングできます。

x = 2

これはおもちゃの例でしたので、もう少し現実を探ってみましょう。

`+ main()+`の循環的複雑度は5です。 コードの各ブランチをコメントして、どこにあるかを確認します。

# cyclomatic_example.py
import sys

def main():
    if len(sys.argv) > 1:  # 1
        filepath = sys.argv[1]
    else:
        print("Provide a file path")
        exit(1)
    if filepath:  # 2
        with open(filepath) as fp:  # 3
            for line in fp.readlines():  # 4
                if line != "\n":  # 5
                    print(line, end="")

if __name__ == "__main__":  # Ignored.
    main()

確かに、コードをはるかに単純な代替手段にリファクタリングできる方法があります。 後で説明します。

注: *Cyclomatic Complexityの尺度は、http://www.literateprogramming.com/mccabe.pdf [Thomas J.によって開発されました] マッケイブ、シニア] 1976年。 McCabeメトリック*または* McCabe番号*と呼ばれることがあります。

次の例では、https://radon.readthedocs.io/[+ radon +]ライブラリhttps://pypi.org/project/radon[from PyPi]を使用してメトリックを計算します。 今すぐインストールできます:

$ pip install radon

`+ radon `を使用して循環的複雑度を計算するには、サンプルを ` cyclomatic_example.py `というファイルに保存し、コマンドラインから ` radon +`を使用します。

`+ radon +`コマンドは2つの主要な引数を取ります:

  1. 分析のタイプ(循環的複雑度の場合は + cc +

  2. 分析するファイルまたはフォルダーへのパス

`+ cyclomatic_example.py `ファイルに対して ` cc `分析を使用して ` radon `コマンドを実行します。 ` -s +`を追加すると、出力に循環的な複雑さが生じます。

$ radon cc cyclomatic_example.py -s
cyclomatic_example.py
    F 4:0 main - B (6)

出力は少し不可解です。 各部分の意味は次のとおりです。

  • 「+ F 」は関数、「 M 」はメソッド、「 C +」はクラスを意味します。

  • `+ main +`は関数の名前です。

  • `+ 4 +`は関数が始まる行です。

  • `+ B +`はAからFまでの評価です。 Aは最高のグレードであり、最も複雑ではありません。

  • 括弧内の数字「6」は、コードの循環的な複雑さです。

Halstead Metrics

Halsteadの複雑さのメトリックは、プログラムのコードベースのサイズに関連しています。 それらはモーリス・Hによって開発されました。 1977年のハルステッド。 Halstead方程式には4つのメジャーがあります。

  • Operands は変数の値と名前です。

  • *演算子*は、すべての組み込みキーワードで、「+ if 」、「 else 」、「 for 」、「 while +」などです。

  • *長さ(N)*は、プログラム内の演算子の数とオペランドの数です。

  • *語彙(h)*は、プログラム内の_unique_演算子の数に_unique_オペランドの数を加えたものです。

これらのメジャーには、さらに3つのメトリックがあります。

  • *ボリューム(V)*は、*長さ*と*語彙*の積を表します。

  • *難易度(D)*は、一意のオペランドの半分とオペランドの再利用の積を表します。

  • *努力(E)*は、*ボリューム*と*難易度*の積である全体的なメトリックです。

これらはすべて非常に抽象的なので、相対的な用語で説明しましょう。

  • 多数の演算子と一意のオペランドを使用する場合、アプリケーションの労力は最大になります。

  • 少数の演算子と少ない変数を使用する場合、アプリケーションの労力は低くなります。

`+ cyclomatic_complexity.py +`の例では、演算子とオペランドの両方が最初の行にあります:

import sys  # import (operator), sys (operand)

「+ import 」は演算子であり、「 sys +」はモジュールの名前であるため、オペランドです。

もう少し複雑な例では、いくつかの演算子とオペランドがあります。

if len(sys.argv) > 1:
    ...

この例には5つの演算子があります。

  1. + if +

  2. +> +

さらに、2つのオペランドがあります。

  1. + sys.argv +

  2. + 1 +

`+ radon +`は演算子のサブセットのみをカウントすることに注意してください。 たとえば、括弧は計算から除外されます。

`+ radon +`のHalstead測定値を計算するには、次のコマンドを実行できます。

$ radon hal cyclomatic_example.py
cyclomatic_example.py:
    h1: 3
    h2: 6
    N1: 3
    N2: 6
    vocabulary: 9
    length: 9
    calculated_length: 20.264662506490406
    volume: 28.529325012980813
    difficulty: 1.5
    effort: 42.793987519471216
    time: 2.377443751081734
    bugs: 0.009509775004326938
  • `+ radon +`が時間とバグの指標を提供するのはなぜですか?*

Halsteadは、努力( + E +)を18で割ることで、コード化にかかる時間を秒単位で推定できると理論付けました。

また、ハルステッドは、予想されるバグの数を推定して、ボリューム( + V +)を3000で割ることもできると述べました。 これは、Pythonが発明される前の1977年に書かれたことに留意してください! パニックに陥らず、まだバグを探し始めてください。

保全性指数

保守性指標は、McCabe Cyclomatic ComplexityとHalstead Volumeの測定値をおよそ0〜100のスケールでもたらします。

興味がある場合、元の方程式は次のとおりです。

この式では、「+ V 」はHalsteadボリュームメトリック、「 C 」は循環的複雑度、「 L +」はコードの行数です。

最初にこの方程式を見たときと同じように困惑している場合、それは次のことを意味します。変数、操作、決定パス、コード行の数を含むスケールを計算します。

多くのツールや言語で使用されているため、より標準的な指標の1つです。 ただし、方程式には多数の修正が加えられているため、正確な数値を事実とみなすべきではありません。 + radon ++ wily +、およびVisual Studioは、0〜100の数値を制限します。

保守性インデックススケールでは、注意が必要なのは、コードが大幅に低くなったとき(0に向かって)です。 スケールでは、25未満は保守が困難であると見なされ、75を超えると保守が容易であると見なされます。 Maintainability Indexは MI とも呼ばれます。

保守性指標は、アプリケーションの現在の保守性を取得し、リファクタリング中に進捗を確認するための尺度として使用できます。

`+ radon +`から保守性インデックスを計算するには、次のコマンドを実行します。

$ radon mi cyclomatic_example.py -s
cyclomatic_example.py - A (87.42)

この結果では、「+ A 」は、「+ラドン」がスケール上の「87.42」の数値に適用したグレードです。 このスケールでは、「+ A 」が最も保守性が高く、「 F +」が最小です。

「+ wily +」を使用してプロジェクトの複雑さをキャプチャおよび追跡する

https://github.com/tonybaloney/wily [`+ wily `は、これまでに取り上げたHalstead、Cyclomatic、LOCなどのコード複雑度メトリックを収集するためのオープンソースソフトウェアプロジェクトです。 ` wily +`はGitと統合され、Gitブランチおよびリビジョン全体でメトリックの収集を自動化できます。

「+ wily +」の目的は、時間の経過とともにコードの複雑さの傾向や変化を確認できるようにすることです。 車を微調整したり、フィットネスを改善したりする場合は、ベースラインの測定と経時的な改善の追跡から始めます。

`+ wily +`のインストール

`+ wily +`はhttps://pypi.org/project/wily/[on PyPi]で利用でき、pipを使用してインストールできます:

$ pip install wily

`+ wily +`がインストールされると、コマンドラインでいくつかのコマンドを使用できます:

  • * + wily build +:* Git履歴を反復処理し、各ファイルのメトリックを分析します

  • * + wily report +:*特定のファイルまたはフォルダーのメトリックの履歴トレンドを参照

  • * + wily graph +:* HTMLファイル内の一連のメトリックをグラフ化する

キャッシュを構築する

`+ wily `を使用する前に、プロジェクトを分析する必要があります。 これは、 ` wily build +`コマンドを使用して行われます。

チュートリアルのこのセクションでは、HTTP APIとの対話に使用される非常に人気のある「+ requests +」パッケージを分析します。 このプロジェクトはオープンソースであり、GitHubで利用できるため、ソースコードのコピーに簡単にアクセスしてダウンロードできます。

$ git clone https://github.com/requests/requests
$ cd requests
$ ls
AUTHORS.rst        CONTRIBUTING.md    LICENSE            Makefile
Pipfile.lock       _appveyor          docs               pytest.ini
setup.cfg          tests              CODE_OF_CONDUCT.md HISTORY.md
MANIFEST.in        Pipfile            README.md          appveyor.yml
ext                requests           setup.py           tox.ini

注意: Windowsユーザーは、従来のMS-DOSコマンドラインの代わりに、次の例にPowerShellコマンドプロンプトを使用する必要があります。 PowerShell CLIを起動するには、[。keys]#Win + R#を押し、「+ powershell +」と入力してから[.keys]#Enter#を入力します。

ここには、テスト、ドキュメント、および構成用のいくつかのフォルダーがあります。 私たちは、 + requests +`というフォルダーにある `+ requests + Pythonパッケージのソースコードにのみ興味があります。

複製されたソースコードから `+ wily build +`コマンドを呼び出し、最初の引数としてソースコードフォルダーの名前を指定します。

$ wily build requests

コンピューターのCPU能力に応じて、分析に数分かかります。

Wilyビルドコマンドのスクリーンショットキャプチャ、幅= 1276、高さ= 257

プロジェクトに関するデータを収集する

`+ requests +`ソースコードを分析したら、任意のファイルまたはフォルダーをクエリして主要なメトリックを確認できます。 チュートリアルの前半で、以下について説明しました。

  • コードの行

  • 保全性指数

  • 巡回複雑度

これらは、 `+ wily `の3つのデフォルトメトリックです。 特定のファイル( ` requests/api.py +`など)のこれらのメトリックを表示するには、次のコマンドを実行します。

$ wily report requests/api.py

`+ wily +`は、各Gitコミットのデフォルトのメトリックに関する表形式のレポートを日付の逆順で出力します。 最新のコミットが一番上に表示され、最も古いコミットが一番下に表示されます。

Revision Author Date MI Lines of Code Cyclomatic Complexity

f37daf2

Nate Prewitt

2019-01-13

100 (0.0)

158 (0)

9 (0)

6dd410f

Ofek Lev

2019-01-13

100 (0.0)

158 (0)

9 (0)

5c1f72e

Nate Prewitt

2018-12-14

100 (0.0)

158 (0)

9 (0)

c4d7680

Matthieu Moy

2018-12-14

100 (0.0)

158 (0)

9 (0)

c452e3b

Nate Prewitt

2018-12-11

100 (0.0)

158 (0)

9 (0)

5a1e738

Nate Prewitt

2018-12-10

100 (0.0)

158 (0)

9 (0)

これは、 `+ requests/api.py +`ファイルに以下があることを示しています。

  • 158行のコード

  • 100の完全な保守性インデックス

  • 9の循環的複雑度

他のメトリックを表示するには、まずそれらの名前を知る必要があります。 これを確認するには、次のコマンドを実行します。

$ wily list-metrics

演算子、コードを分析するモジュール、およびそれらが提供するメトリックのリストが表示されます。

レポートコマンドで代替メトリックを照会するには、ファイル名の後にそれらの名前を追加します。 必要な数のメトリックを追加できます。 保守性ランクとソースコード行の例を次に示します。

$ wily report requests/api.py maintainability.rank raw.sloc

テーブルには、代替メトリックを含む2つの異なる列が表示されます。

グラフ化メトリック

メトリックの名前とコマンドラインでのクエリ方法がわかったので、それらをグラフで視覚化することもできます。 `+ wily +`は、レポートコマンドと同様のインターフェースを備えたHTMLおよびインタラクティブチャートをサポートします。

$ wily graph requests/sessions.py maintainability.mi

デフォルトのブラウザが開き、次のようなインタラクティブなチャートが表示されます。

Wily graphコマンドのスクリーンショットキャプチャ、width = 1276、height = 860

特定のデータポイントにカーソルを合わせると、Gitのコミットメッセージとデータが表示されます。

HTMLファイルをフォルダーまたはリポジトリに保存する場合は、ファイルへのパスに `+ -o +`フラグを追加できます。

$ wily graph requests/sessions.py maintainability.mi -o my_report.html

これで、他の人と共有できる「+ my_report.html +」というファイルが作成されます。 このコマンドは、チームのダッシュボードに最適です。

+ pre-commit +`フックとしての `+ wily +

プロジェクトに変更をコミットする前に、複雑さの改善または低下を警告できるように、「+ wily +」を設定できます。

`+ wily `には ` wily diff +`コマンドがあり、最後にインデックス付けされたデータをファイルの現在の作業コピーと比較します。

`+ wily diff `コマンドを実行するには、変更したファイルの名前を指定します。 たとえば、 ` requests/api.py `に変更を加えた場合、ファイルパスで ` wily diff +`を実行すると、メトリックへの影響がわかります。

$ wily diff requests/api.py

応答では、変更されたすべてのメトリックと、循環的な複雑さのために変更された関数またはクラスが表示されます。

`+ diff `コマンドは、 ` pre-commit `と呼ばれるツールと組み合わせることができます。 ` pre-commit `は、 ` git commit +`コマンドを実行するたびにスクリプトを呼び出すフックをGit設定に挿入します。

`+ pre-commit +`をインストールするには、PyPIからインストールできます:

$ pip install pre-commit

プロジェクトのルートディレクトリの `+ .pre-commit-config.yaml +`に次を追加します。

repos:
-   repo: local
    hooks:
    -   id: wily
        name: wily
        entry: wily diff
        verbose: true
        language: python
        additional_dependencies: [wily]

これを設定したら、 `+ pre-commit install +`コマンドを実行して物事を完成させます:

$ pre-commit install

`+ git commit `コマンドを実行すると、ステージングされた変更に追加したファイルのリストとともに ` wily diff +`が呼び出されます。

`+ wily +`は、コードの複雑さをベースライン化し、リファクタリングを開始するときに行った改善を測定するのに役立つユーティリティです。

Pythonでのリファクタリング

リファクタリングとは、アプリケーション(コードまたはアーキテクチャ)を変更して、外部で同じように動作するが、内部的に改善する手法です。 これらの改善は、安定性、パフォーマンス、または複雑さの軽減です。

世界最古の地下鉄の1つであるロンドン地下鉄は、1863年にメトロポリタン線が開通したときに始まりました。 蒸気機関車で運ばれるガス灯のある木製の馬車がありました。 鉄道の開通時には、目的に合っていました。 1900年に電気鉄道の発明がもたらされました。

1908年までに、ロンドン地下鉄は8本の鉄道に拡大しました。 第二次世界大戦中、ロンドンの地下鉄の駅は列車に閉鎖され、防空sheとして使用されました。 現代のロンドン地下鉄は、270以上の駅で1日数百万人の乗客を運んでいます。

初めて完全なコードを記述することはほとんど不可能であり、要件は頻繁に変更されます。 2020年に鉄道の元の設計者に1日1,000万人の乗客に適したネットワークを設計するように依頼した場合、今日存在するネットワークは設計されません。

代わりに、鉄道は都市の変化に合わせて運行、設計、レイアウトを最適化するために一連の継続的な変更を受けています。 リファクタリングされました。

このセクションでは、テストとツールを活用して安全にリファクタリングする方法を探ります。 また、https://code.visualstudio.com/[Visual Studio Code]およびhttps://www.jetbrains.com/pycharm/[PyCharm]でリファクタリング機能を使用する方法も確認できます。

リファクタリングによるリスクの回避:ツールの活用とテストの実施

リファクタリングのポイントが外部に影響を与えずにアプリケーションの内部を改善することである場合、外部が変更されていないことをどのように確認しますか?

主要なリファクタリングプロジェクトに突入する前に、アプリケーションの堅牢なテストスイートがあることを確認する必要があります。 理想的には、テストスイートはほとんど自動化されている必要があります。これにより、変更を加えたときにユーザーへの影響を確認し、迅速に対処できます。

Pythonでのテストについて詳しく知りたい場合は、https://realpython.com/python-testing/[Pythonでのテストの開始]を開始するのに最適な場所です。

アプリケーションに対して行うテストの完全な数はありません。 ただし、テストスイートの堅牢性と徹底性が高いほど、コードをより積極的にリファクタリングできます。

リファクタリングを行うときに実行する最も一般的な2つのタスクは次のとおりです。

  • モジュール、関数、クラス、およびメソッドの名前変更

  • 関数、クラス、およびメソッドの使用法を見つけて、それらが呼び出される場所を確認する

これは、 search and replace を使用して手作業で簡単に実行できますが、時間がかかり、リスクも伴います。 代わりに、これらのタスクを実行するための優れたツールがいくつかあります。

リファクタリングに `+ rope +`を使用する

`+ rope +`は、Pythonコードをリファクタリングするための無料のPythonユーティリティです。 Pythonコードベースのコンポーネントをリファクタリングおよび名前変更するためのhttps://github.com/python-rope/rope/blob/master/docs/overview.rst[extensive] APIセットが付属しています。

`+ rope +`は2つの方法で使用できます。

  1. エディタープラグインを使用して、https://marketplace.visualstudio.com/items?itemName = ms-python.python [Visual Studio Code]、https://github.com/python-rope/ropemacs [Emacs]、またはhttps://github.com/python-rope/ropevim[Vim]

  2. アプリケーションをリファクタリングするスクリプトを作成して直接

ropeをライブラリとして使用するには、まず `+ pip `を実行して ` rope +`をインストールします。

$ pip install rope

REPLで `+ rope `を使用すると、プロジェクトを探索して変更をリアルタイムで確認できるため便利です。 開始するには、 ` Project +`タイプをインポートし、プロジェクトへのパスでインスタンス化します:

>>>

>>> from rope.base.project import Project

>>> proj = Project('requests')

`+ proj `変数は、特定のファイルを取得するために、 ` get_files `や ` get_file `などの一連のコマンドを実行できるようになりました。 ファイル「 api.py 」を取得し、「 api +」という変数に割り当てます。

>>>

>>> [f.name for f in proj.get_files()]
['structures.py', 'status_codes.py', ...,'api.py', 'cookies.py']

>>> api = proj.get_file('api.py')

このファイルの名前を変更したい場合は、ファイルシステム上で単純に名前を変更できます。 ただし、プロジェクト内の古い名前をインポートした他のPythonファイルは破損します。 `+ api.py `の名前を ` new_api.py +`に変更しましょう。

>>>

>>> from rope.refactor.rename import Rename

>>> change = Rename(proj, api).get_changes('new_api')

>>> proj.do(change)

`+ git status `を実行すると、 ` rope +`がリポジトリにいくつかの変更を加えたことがわかります。

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   requests/__init__.py
    deleted:    requests/api.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

   requests/.ropeproject/
   requests/new_api.py

no changes added to commit (use "git add" and/or "git commit -a")

`+ rope +`による3つの変更は次のとおりです。

  1. `+ requests/api.py `を削除し、 ` requests/new_api.py +`を作成しました

  2. 「+ api 」ではなく「 new_api 」からインポートするように「 requests/ init 。py +」を変更しました

  3. `+ .ropeproject +`という名前のプロジェクトフォルダを作成しました

変更をリセットするには、「+ git reset +」を実行します。

`+ rope +`で実行できるhttps://github.com/python-rope/rope/blob/master/docs/overview.rst#refactorings [他の数百のリファクタリング]があります。

リファクタリングのためのVisual Studioコードの使用

Visual Studio Codeは、独自のUIを通じて `+ rope +`で使用可能なリファクタリングコマンドの小さなサブセットを開きます。

あなたはできる:

  1. ステートメントから変数を抽出する

  2. コードブロックからメソッドを抽出する

  3. インポートを論理的な順序に並べ替える

コマンドパレットから_Extract methods_コマンドを使用する例を次に示します。

Visual Studio Codeリファクタリングのスクリーンショット、幅= 1493、高さ= 1080

リファクタリングにPyCharmを使用する

PythonエディターとしてPyCharmを使用している、または使用を検討している場合、PyCharmの強力なリファクタリング機能に注意する価値があります。

WindowsおよびmacOSでは、[。keys]#Ctrl + T#コマンドを使用して、すべてのリファクタリングショートカットにアクセスできます。 Linuxでリファクタリングにアクセスするためのショートカットは[.keys]#Ctrl + Shift + Alt + T#です。

呼び出し元と関数とクラスの使用法を見つける

メソッドまたはクラスを削除したり、動作を変更したりする前に、それに依存するコードを知る必要があります。 PyCharmは、プロジェクト内のメソッド、関数、またはクラスのすべての使用法を検索できます。

この機能にアクセスするには、メソッド、クラス、または変数を右クリックして選択し、_Find Usages_を選択します。

PyCharm

検索条件を使用するすべてのコードは、下部のパネルに表示されます。 任意のアイテムをダブルクリックして、問題の行に直接移動できます。

PyCharmリファクタリングツールの使用

他のリファクタリングコマンドには、次の機能が含まれます。

  • 既存のコードからメソッド、変数、定数を抽出する

  • 抽象メソッドを指定する機能など、既存のクラスシグネチャから抽象クラスを抽出する

  • 変数からメソッド、ファイル、クラス、またはモジュールまで、実質的にすべての名前を変更します

以下は、 `+ rope `モジュールを使用して以前に名前を変更した同じ ` api.py `モジュールの名前を ` new_api.py +`に変更する例です。

pycharm

renameコマンドはUIにコンテキスト化されているため、リファクタリングが迅速かつ簡単になります。 新しいモジュール名で `+ init 。py +`のインポートを自動的に更新しました。

別の便利なリファクタリングは、_Change Signature_コマンドです。 これを使用して、関数またはメソッドに引数を追加、削除、または名前変更できます。 使用法を検索し、それらを更新します。

デフォルト値を設定し、リファクタリングが新しい引数を処理する方法を決定することもできます。

概要

リファクタリングは、開発者にとって重要なスキルです。 この章で学んだように、あなたは一人ではありません。 ツールとIDEには、強力なリファクタリング機能がすでに付属しており、すばやく変更を加えることができます。

複雑さの反パターン

複雑さの測定方法、測定方法、およびコードのリファクタリング方法がわかったので、次はコードを必要以上に複雑にする5つの一般的なアンチパターンを学習します。

Anti-Patterns目次、幅= 1667 、height = 626

これらのパターンを習得し、それらをリファクタリングする方法を知っていれば、すぐに保守性の高いPythonアプリケーションを軌道に乗せることができます。

1. オブジェクトになる関数

Pythonは、関数とhttps://realpython.com/python3-object-oriented-programming/[継承可能なクラス]を使用してhttps://en.wikipedia.org/wiki/Procedural_programming[procedural programming]をサポートします。 どちらも非常に強力であり、さまざまな問題に適用する必要があります。

画像を操作するためのモジュールの例をご覧ください。 簡潔にするため、関数のロジックは削除されました。

# imagelib.py

def load_image(path):
    with open(path, "rb") as file:
        fb = file.load()
    image = img_lib.parse(fb)
    return image

def crop_image(image, width, height):
    ...
    return image

def get_image_thumbnail(image, resolution=100):
    ...
    return image

この設計にはいくつかの問題があります。

  1. `+ crop_image()`と ` get_image_thumbnail()`が元の ` image +`変数を変更するか、新しい画像を作成するかどうかは明らかではありません。 画像をロードし、トリミングされた画像とサムネイル画像の両方を作成する場合、最初にインスタンスをコピーする必要がありますか? 関数のソースコードを読むことはできますが、これを行うすべての開発者に頼ることはできません。

  2. 画像関数を呼び出すたびに、画像変数を引数として渡す必要があります。

呼び出し元のコードは次のようになります。

from imagelib import load_image, crop_image, get_image_thumbnail

image = load_image('~/face.jpg')
image = crop_image(image, 400, 500)
thumb = get_image_thumbnail(image)

次に、クラスにリファクタリングできる関数を使用したコードの症状をいくつか示します。

  • 関数間で同様の引数 より多くのHalstead + h2 + 一意のオペランド*

  • 可変関数と不変関数の混合

  • 複数のPythonファイルにまたがる関数

これらの3つの関数のリファクタリングバージョンを以下に示します。

  • `+ . init ()`は ` load_image()+`を置き換えます。

  • `+ crop()+`はクラスメソッドになります。

  • `+ get_image_thumbnail()+`はプロパティになります。

サムネイルの解像度はクラスプロパティになっているため、グローバルに、またはその特定のインスタンスで変更できます。

# imagelib.py

class Image(object):
    thumbnail_resolution = 100
    def __init__(self, path):
        ...

    def crop(self, width, height):
        ...

    @property
    def thumbnail(self):
        ...
        return thumb

このコードにさらに多くの画像関連の関数がある場合、クラスへのリファクタリングは劇的な変更を加える可能性があります。 次の考慮事項は、消費するコードの複雑さです。

これは、リファクタリングされた例の外観です。

from imagelib import Image

image = Image('~/face.jpg')
image.crop(400, 500)
thumb = image.thumbnail

結果のコードで、元の問題を解決しました。

  • `+ thumbnail +`はプロパティであるためサムネイルを返し、インスタンスを変更しないことは明らかです。

  • コードでは、トリミング操作のために新しい変数を作成する必要がなくなりました。

2. 関数であるべきオブジェクト

時には、その逆の場合もあります。 1つまたは2つの単純な関数により適したオブジェクト指向コードがあります。

クラスの誤った使用のいくつかの物語の兆候は次のとおりです。

  • 1つのメソッドを持つクラス( `+ . init ()+`以外)

  • 静的メソッドのみを含むクラス

次の認証クラスの例をご覧ください。

# authenticate.py

class Authenticator(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        ...
        return result

引数として `+ username `と ` password `をとる ` authenticate()+`という名前の単純な関数を用意する方がより意味があります。

# authenticate.py

def authenticate(username, password):
    ...
    return result

座ってこれらの基準に一致するクラスを手動で探す必要はありません。`+ pylint + `には、クラスに少なくとも2つのパブリックメソッドが必要であるというルールが付属しています。 PyLintおよびその他のコード品質ツールの詳細については、https://realpython.com/python-code-quality/#linters [Python Code Quality]をご覧ください。

`+ pylint +`をインストールするには、コンソールで次のコマンドを実行します。

$ pip install pylint

`+ pylint `はいくつかのオプションの引数を取り、次に1つ以上のファイルとフォルダーへのパスを取ります。 デフォルト設定で ` pylint `を実行すると、 ` pylint `には膨大な数のルールがあるため、多くの出力が得られます。 代わりに、特定のルールを実行できます。 ` too-few-public-methods `ルールIDは ` R0903 +`です。 これはhttps://docs.pylint.org/en/latest/technical_reference/features.html [ドキュメントWebサイト]で確認できます。

$ pylint --disable=all --enable=R0903 requests
************* Module requests.auth
requests/auth.py:72:0: R0903: Too few public methods (1/2) (too-few-public-methods)
requests/auth.py:100:0: R0903: Too few public methods (1/2) (too-few-public-methods)
************* Module requests.models
requests/models.py:60:0: R0903: Too few public methods (1/2) (too-few-public-methods)

-----------------------------------
Your code has been rated at 9.99/10

この出力から、 `+ auth.py `にはpublicメソッドが1つしかない2つのクラスが含まれていることがわかります。 これらのクラスは、72行目と100行目にあります。 ` models.py +`の60行目には、パブリックメソッドが1つだけのクラスもあります。

3. 「三角」コードからフラットコードへの変換

ソースコードをズームアウトして頭を右に90度傾けると、空白はオランダのように平らに見えますか、ヒマラヤのように山のように見えますか? 山岳コードは、コードに多くのネストが含まれていることを示しています。

*Zen of Python *の原則の1つを次に示します。

_ _ 「フラットはネストよりも優れています」

— Pythonの禅、ティムピーターズ _ _

ネストされたコードよりフラットなコードの方が優れているのはなぜですか? ネストされたコードは、何が起こっているかを読み、理解するのを難しくするからです。 読者は、ブランチを通過する際の条件を理解し、記憶する必要があります。

これらは、高度にネストされたコードの症状です。

  • コードブランチの数のために高度な循環的複雑さ *コードの行数に比べて循環的複雑度が高いため、保守性指数が低い

単語 `+ error `に一致する文字列の引数 ` data `を調べるこの例を取り上げます。 最初に、 ` data `引数がリストかどうかをチェックします。 次に、それぞれを反復処理し、アイテムが文字列かどうかを確認します。 文字列で値が `" error "`の場合、 ` True `を返します。 そうでなければ、 ` False +`を返します:

def contains_errors(data):
    if isinstance(data, list):
        for item in data:
            if isinstance(item, str):
                if item == "error":
                    return True
    return False

この関数は小さいため、保守性指数が低くなりますが、循環的複雑度が高くなります。

代わりに、「早期に戻る」ことでネストのレベルを削除し、「+ data 」の値がリストでない場合に「 False 」を返すことで、この関数をリファクタリングできます。 次に、リストオブジェクトで ` .count()`を使用して、 `" error "`のインスタンスをカウントします。 戻り値は、 ` .count()+`がゼロより大きいという評価です:

def contains_errors(data):
    if not isinstance(data, list):
        return False
    return data.count("error") > 0

ネストを減らす別の方法は、リストの内包表記を活用することです。 新しいリストを作成するこの一般的なパターンは、リスト内の各アイテムを調べて基準に一致するかどうかを確認し、すべての一致を新しいリストに追加します。

results = []
for item in iterable:
    if item == match:
        results.append(item)

このコードは、より高速で効率的なリスト内包表記に置き換えることができます。

最後の例をリファクタリングしてリストの内包表記と `+ if +`ステートメントにします:

results = [item for item in iterable if item == match]

この新しい例はより小さく、複雑さが少なく、パフォーマンスが向上しています。

データが単一のディメンションリストではない場合、データ構造からイテレータを作成するための関数を含む標準ライブラリのhttps://docs.python.org/3/library/itertools.html[itertools]パッケージを活用できます。 。 これを使用して、イテラブルを連鎖させたり、構造をマッピングしたり、既存のイテラブルを繰り返したり繰り返したりすることができます。

Itertoolsには、https://docs.python.org/3/library/itertools.html#itertools.filterfalse [+ filterfalse()+]などのデータをフィルタリングするための関数も含まれています。 Itertoolsの詳細については、https://realpython.com/python-itertools/[Python 3、Itertools in By Example]をご覧ください。

4. クエリツールを使用した複雑な辞書の処理

Pythonの最も強力で広く使用されているコアタイプの1つは辞書です。 速く、効率的で、スケーラブルで、非常に柔軟です。

辞書を初めて使用する場合、または辞書をさらに活用できると思われる場合は、https://realpython.com/python-dicts/[Pythonの辞書]で詳細をご覧ください。

1つの大きな副作用があります。辞書が高度にネストされている場合、それらを照会するコードもネストされます。

先ほど見た東京メトロの路線のサンプルであるこのデータの例をご覧ください。

data = {
 "network": {
  "lines": [
    {
     "name.en": "Ginza",
     "name.jp": "銀座線",
     "color": "orange",
     "number": 3,
     "sign": "G"
    },
    {
     "name.en": "Marunouchi",
     "name.jp": "丸ノ内線",
     "color": "red",
     "number": 4,
     "sign": "M"
    }
  ]
 }
}

特定の番号に一致する行を取得したい場合、これは小さな関数で実現できます。

def find_line_by_number(data, number):
    matches = [line for line in data if line['number'] == number]
    if len(matches) > 0:
        return matches[0]
    else:
        raise ValueError(f"Line {number} does not exist.")

関数自体は小さいですが、データが非常にネストされているため、関数の呼び出しは不必要に複雑です。

>>>

>>> find_line_by_number(data["network"]["lines"], 3)

Pythonで辞書を照会するためのサードパーティツールがあります。 最も人気のあるものは、http://jmespath.org/[JMESPath]、https://github.com/mahmoud/glom [glom]、https://asq.readthedocs.io/en/latest/[asq]です。 、およびhttps://flupy.readthedocs.io/en/latest/[flupy]。

JMESPathは私たちの鉄道ネットワークを支援します。 JMESPathはJSON用に設計されたクエリ言語であり、Python辞書で動作するPython用のプラグインを使用できます。 JMESPathをインストールするには、次の手順を実行します。

$ pip install jmespath

次に、Python REPLを開いてJMESPath APIを調べ、 `+ data `辞書をコピーします。 開始するには、 ` jmespath `をインポートし、最初の引数としてクエリ文字列を、2番目の引数としてデータを指定して ` search()`を呼び出します。 クエリ文字列 `" network.lines "`は、 ` data ['network'] ['lines'] +`を返すことを意味します。

>>>

>>> import jmespath

>>> jmespath.search("network.lines", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線',
  'color': 'orange', 'number': 3, 'sign': 'G'},
 {'name.en': 'Marunouchi', 'name.jp': '丸ノ内線',
  'color': 'red', 'number': 4, 'sign': 'M'}]

リストを操作するときは、角括弧を使用して内部でクエリを提供できます。 「すべて」のクエリは単純に「+* +」です。 その後、一致する各アイテム内に属性の名前を追加して返すことができます。 すべての行の行番号を取得したい場合、これを行うことができます:

>>>

>>> jmespath.search("network.lines[*].number", data)
[3, 4]

`+ == `や ` <+`のような、より複雑なクエリを提供できます。 Pythonの開発者にとって構文は少し珍しいので、参照用にhttp://jmespath.org/examples.html[documentation]を手元に置いておきます。

番号が「3」の行を見つけたい場合は、1つのクエリでこれを実行できます。

>>>

>>> jmespath.search("network.lines[?number==`3`]", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線', 'color': 'orange', 'number': 3, 'sign': 'G'}]

その線の色を取得したい場合は、クエリの最後に属性を追加できます。

>>>

>>> jmespath.search("network.lines[?number==`3`].color", data)
['orange']

JMESPathを使用して、複雑な辞書を照会および検索するコードを削減および簡素化できます。

5. `+ attrs `と ` dataclasses +`を使用してコードを削減する

リファクタリングのもう1つの目標は、同じ動作を実現しながらコードベース内のコードの量を単純に減らすことです。 これまでに示した手法は、コードをより小さくシンプルなモジュールにリファクタリングするのに大いに役立ちます。

他のいくつかの手法では、標準ライブラリといくつかのサードパーティライブラリの知識が必要です。

ボイラープレートとは

定型コードは、ほとんどまたはまったく変更せずに多くの場所で使用する必要があるコードです。

電車のネットワークを例にとると、PythonクラスとPython 3タイプヒントを使用してそれを型に変換する場合、次のようになります。

from typing import List

class Line(object):
    def __init__(self, name_en: str, name_jp: str, color: str, number: int, sign: str):
        self.name_en = name_en
        self.name_jp = name_jp
        self.color = color
        self.number = number
        self.sign = sign

    def __repr__(self):
        return f"<Line {self.name_en} color='{self.color}' number={self.number} sign='{self.sign}'>"

    def __str__(self):
        return f"The {self.name_en} line"

class Network(object):
    def __init__(self, lines: List[Line]):
        self._lines = lines

    @property
    def lines(self) -> List[Line]:
        return self._lines

ここで、 `+ . eq ()+`のような他のマジックメソッドを追加することもできます。 このコードは定型です。 ここにはビジネスロジックやその他の機能はありません。データをある場所から別の場所にコピーするだけです。

`+ dataclasses +`のケース

Python 3.7の標準ライブラリに導入され、PyPI上のPython 3.6のバックポートパッケージにより、dataclassesモジュールは、データを保存するだけのこれらのタイプのクラスの多くの定型を削除するのに役立ちます。

上記の `+ Line +`クラスをデータクラスに変換するには、すべてのフィールドをクラス属性に変換し、型注釈があることを確認します。

from dataclasses import dataclass

@dataclass
class Line(object):
    name_en: str
    name_jp: str
    color: str
    number: int
    sign: str

次に、以前と同じ引数、同じフィールド、さらには「+ . str ()」、「 . repr ()」、「 . eq」を使用して、「+ Line 」型のインスタンスを作成できます() `が実装されています:

>>>

>>> line = Line('Marunouchi', "丸ノ内線", "red", 4, "M")

>>> line.color
red

>>> line2 = Line('Marunouchi', "丸ノ内線", "red", 4, "M")

>>> line == line2
True

データクラスは、標準ライブラリで既に利用可能な単一のインポートでコードを削減するための優れた方法です。 完全なウォークスルーについては、https://realpython.com/python-data-classes/[Python 3.7のデータクラスの究極のガイド]をご覧ください。

いくつかの `+ attrs +`の使用例

「+ attrs 」はサードパーティのパッケージであり、データクラスよりもずっと長く使用されています。 ` attrs +`にはさらに多くの機能があり、Python 2.7および3.4​​+で使用できます。

Python 3.5以前を使用している場合、 `+ attrs `は ` dataclasses +`の優れた代替手段です。 また、さらに多くの機能を提供します。

`+ attrs `の同等のデータクラスの例は似ています。 型注釈を使用する代わりに、クラス属性には ` attrib()+`の値が割り当てられます。 これは、デフォルト値や入力を検証するためのコールバックなど、追加の引数を取ることができます。

from attr import attrs, attrib

@attrs
class Line(object):
    name_en = attrib()
    name_jp = attrib()
    color = attrib()
    number = attrib()
    sign = attrib()

`+ attrs +`は、データクラスの定型コードと入力検証を削除するための便利なパッケージです。

結論

複雑なコードを特定して対処する方法を学習したので、アプリケーションを簡単に変更および管理できるようにするために実行できる手順を思い出してください。

  • `+ wily +`などのツールを使用してプロジェクトのベースラインを作成することから始めます。

  • いくつかのメトリックを見て、最も保守性の低いインデックスを持つモジュールから始めます。

  • テストで提供された安全性とPyCharmや `+ rope +`などのツールの知識を使用して、そのモジュールをリファクタリングします。

これらの手順とこの記事のベストプラクティスに従えば、新しい機能の追加やパフォーマンスの改善など、アプリケーションに他のエキサイティングなことを行うことができます。