【はじめに】リファクタリングとは、コードの機能を変えずにコードの品質を向上させるプロセスです。コードを送信する前にコードをリファクタリングするために時間を費やすと、コードが読みやすくなり、保守が容易になります。これを続けると、エラーが見つかり、悪夢のような地獄のシーンから公園の散歩まで新しい機能が追加されます。
理想的には、単体テストはコードを完全にカバーし、自信を持って変更を加えることができます。そうでない場合は、可能な限りリファクタリングする前にテストを追加する必要があります。ただし、テストを追加できない場合でも、自動化された安全なリファクタリングツールを使用することで多くの目標を達成できます。
では、どこから始めますか?この記事には、コードをより読みやすく、より美しくするためにすぐに実行できる5つの簡単なことが含まれています。
1.コメントアウトされたコードを削除します
これは最も単純なリファクタリングであり、コードベースに優れた結果をもたらすことができます。
コメントアウトされたコードは紛らわしいです。それはコードベースを肥大化し、コードの真の実行パスをたどることを困難にします。また、実際のコメントの妨げにもなり、これらのコメントは、古いコード行の多くで失われる可能性があります。
# lots of
#
# commented out lines of code
do_something_important()
#
# even more
# commented out code
このような例では、コメントをスキップしようとするため、コードを読むときに重要な情報が失われる可能性があります。
コメントアウトされたコードがいつか役立つか心配な場合は、ソース管理に保存するのが正しい方法です。必要な場合は、コミット履歴にあります。コメントが削除された場所に適切な名前を付けるだけで、簡単に見つけることができます。
最も重要なことは、コメントアウトされた行のみを注意深く削除する限り、このリファクタリングによってコードが破損することはありません。
次に、数字と文字列を抽出します
開発プロセスでは、通常、文字列リテラルと数値をコードに直接書き込む方が簡単です。ただし、それらを残すことは問題を解決するための良い方法です。
番号または文字列が後で変更された場合は、テキストのすべてのインスタンスを検索し、変更する必要があるかどうかを確認してから、変更する必要があります。このように繰り返しコードを繰り返し変更することは、使用法の1つを見逃しやすいため、エラーの主な原因の1つです。それらを定数に置き換えると、テキストは1つの場所に保存され、その場所でのみ変更する必要があります。
もう1つの問題は、この例の数値のような数値では、その値や目的がある理由がわからないことです。
def update_text(text):
if len(text) > 80:
wrap(text)
定数に置き換えて、わかりやすい名前を付けてください。これにより、コードが読みやすく、理解しやすくなります。
MAX_LINE_LENGTH = 80
def update_text(text):
if len(text) > MAX_LINE_LENGTH:
wrap(text)
実際に変更を加える場合、多くのIDEは、「定数の抽出」オプションを使用してテキストを抽出するのに役立ちます。
3、重複を削除します
DRY(繰り返してはいけません)は、ソフトウェア開発の基本原則の1つです。繰り返すとコードが理解しにくくなり、繰り返されるコードがあいまいさを生み出し始めると、エラーが発生することがよくあります。
重複を削除する簡単な方法は、コードを改善する機会を見つけた場合です。これは、条件の2つのブランチでコードが繰り返し実行される場所であるため、外部で使用できます。
例01次のように、変数には多くの繰り返しがあります。
- 再建前
def get_bm_page(self, account, proxy, page_id, user_id):
url_owned = f"https://test.com/v8.0/{user_id}/owned_pages?fields=id,name&access_token=Easas..&limit=100"
owner_page= self.check_page_id(url_owned, account, proxy, page_id, user_id)
if not owner_page:
url_client = f"https://test.com/v8.0/{user_id}/client_pages?fields=id,name&access_token=Easas..&limit=100"
owner_page self.check_page_id(url_owned, account, proxy, page_id, user_id)
return owner_page
- 再建後
def get_bm_page_id(account, proxy, page_id, user_id):
base_url = "https://test.com/v8.0/{user_id}/%s?fields=id,name&access_token={access_token}&limit=100".format(bm_id='12',access_token='Easas..')
owner_page = False
for page_type in ['owned_pages', 'client_pages']:
owner_page = check_page_id(base_url % page_type, account, proxy, page_id, user_id)
if owner_page:
break
return owner_page
例02コードが関数の異なる部分または2つの異なる関数で繰り返されることがより一般的です。
ここでは、コードを別の関数に抽出し、意味のある名前を付けてから、抽出したコードの代わりに呼び出す必要があります。次のように:
- 再建前
class Game:
# ...
def was_correctly_answered(self):
if self.not_penalised():
print('Answer was correct!!!!')
self.purses[self.current_player] += 1
print(
self.players[self.current_player]
+ ' now has '
+ str(self.purses[self.current_player])
+ ' Gold Coins.'
)
winner = self._did_player_win()
self.current_player += 1
if self.current_player == len(self.players):
self.current_player = 0
return winner
else:
self.current_player += 1
if self.current_player == len(self.players):
self.current_player = 0
return True
def wrong_answer(self):
print('Question was incorrectly answered')
print(self.players[self.current_player] + " was sent to the penalty box")
self.in_penalty_box[self.current_player] = True
self.current_player += 1
if self.current_player == len(self.players):
self.current_player = 0
return True
よく見てください。以下は、3つの場所に表示される明確な反復コードです。
self.current_player += 1
if self.current_player == len(self.players):
self.current_player = 0
これは関数に抽出する必要があります。多くのIDEでは、コードスニペットを選択して自動的に抽出できます。また、一部のPyCharmなどでは、コードをスキャンして、新しい関数を呼び出すことでどの部分を置き換えることができるかを確認します。このメソッドを抽出してnext_playerと呼びましょう。これは、current_playerで次の有効な値に移動するためです。
- 再建後
class Game:
# ...
def was_correctly_answered(self):
if self.not_penalised():
print('Answer was correct!!!!')
self.purses[self.current_player] += 1
print(
self.players[self.current_player]
+ ' now has '
+ str(self.purses[self.current_player])
+ ' Gold Coins.'
)
winner = self._did_player_win()
self.next_player()
return winner
else:
self.next_player()
return True
def wrong_answer(self):
print('Question was incorrectly answered')
print(self.players[self.current_player] + " was sent to the penalty box")
self.in_penalty_box[self.current_player] = True
self.next_player()
return True
def next_player(self):
self.current_player += 1
if self.current_player == len(self.players):
self.current_player = 0
このコードではまだ多くのリファクタリングを行う必要がありますが、確実に改善することができます。next_player()で関数を変更する必要がある場合は、コードを3か所ではなく1か所で変更するだけで済みます。
より多くのコーディングとリファクタリングを行うと、重複を削除する機会がますます増えます。通常、複数のコードを処理して類似性を高めてから、共通の要素を関数に抽出する必要があります。
第四に、大きな関数を分割します
機能が長ければ長いほど、読みにくく、理解しにくくなります。適切に記述された関数の重要な側面は、1つのことを実行し、その機能を正確に反映する名前を付ける必要があることです。これは、いわゆる「単一責任の原則」です。長い関数は多くの異なることをする傾向があります。
保管期間については意見が異なりますが、一般的には短いほど良いです。鋳鉄のルールは、コードエディタのページに確実に収まるようにすることです。機能が何をするかを確認するには、上下にスクロールする必要があります。これにより、機能を理解するために必要な認知的負担が増大します。
関数を分割するには、前のセクションで説明したように、関数の一部を他の関数に抽出する必要があります。
続行する方法を理解するために、いくつかのコードを見てみましょう。
def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
kettle.fill(tap)
kettle.switch_on()
kettle.wait_until_boiling()
boiled_kettle = kettle.pick_up()
teapot.add(tea_bag)
teapot.add(boiled_kettle.pour())
teapot.wait_until_brewed()
full_teapot = teapot.pick_up()
cup.add(full_teapot.pour())
cup.add(milk)
cup.add(sugar)
cup.stir()
return cup
ここでは、呼び出しが3つのカテゴリに分類されていることがわかります。それぞれ、ケトル、ティーポット、カップのメソッドを呼び出します。私たちの専門知識によると(英国人はこれに役立つかもしれません)、これらはお茶を作る3つの段階にも対応しています-やかんを沸騰させ、お茶を淹れ、それをカップに注ぎ、それを食べる。
最後から始めて、お茶を注いでテーブルに出すことに関連するコンテンツを抽出しましょう。これは手動で行うことも、IDEの「メソッドの抽出」機能を使用することもできます。
def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
kettle.fill(tap)
kettle.switch_on()
kettle.wait_until_boiling()
boiled_kettle = kettle.pick_up()
teapot.add(tea_bag)
teapot.add(boiled_kettle.pour())
teapot.wait_until_brewed()
full_teapot = teapot.pick_up()
return pour_tea(cup, full_teapot, milk, sugar)
def pour_tea(cup, full_teapot, milk, sugar):
cup.add(full_teapot.pour())
cup.add(milk)
cup.add(sugar)
cup.stir()
return cup
この段階では、make_tea関数に複数のレベルの抽象化が混在しています。pour_tea関数の実行に関連する複数のステップと比較して、ケトルを開く方が簡単な操作です。理想的には、関数の各行は同じレベルにあり、一貫性のあるステートメントに解析しやすくする必要があります。これを実現するために、先に進んで他の2つの関数を抽出しましょう。
def make_tea(kettle, tap, teapot, tea_bag, cup, milk, sugar):
boiled_kettle = boil_water(kettle, tap)
full_teapot = brew_tea(boiled_kettle, tea_bag, teapot)
return pour_tea(cup, full_teapot, milk, sugar)
def boil_water(kettle, tap):
kettle.fill(tap)
kettle.switch_on()
kettle.wait_until_boiling()
return kettle.pick_up()
def brew_tea(boiled_kettle, tea_bag, teapot):
teapot.add(tea_bag)
teapot.add(boiled_kettle.pour())
teapot.wait_until_brewed()
return teapot.pick_up()
def pour_tea(cup, full_teapot, milk, sugar):
cup.add(full_teapot.pour())
cup.add(milk)
cup.add(sugar)
cup.stir()
return cup
一番上のmake_tea関数をチェックしてください。お茶を作るのと同じように、3つの短編小説を読むことができます。いずれかの段階の詳細に興味がある場合は、関連する方法について詳しく知ることができます。
関数を分割するときは、一緒に何かを実行できる論理的に一貫したコードビットを特定するのが最善です。経験則では、新しい関数のドメイン名に関連する適切な名前を思いつくことができれば、それを抽出することをお勧めします。
5、ローカル変数宣言をその使用法に近い位置に移動します
特定のコードセグメントを読み取るときに、処理する必要のある変数をできるだけ少なくすることが重要です。変数宣言とその使用法の厳密さと範囲を維持することは、これに役立ちます。基本的に、変数がその使用法に近いほど宣言されるほど、後でコードを読み取るときに上下にスキャンする回数が少なくなります。
def assess_fruit(self, fruit):
happiness = 0
hunger = time_since_breakfast() / size_of_breakfast()
some_other_code()
# work out some things
do_some_other_things()
if is_snack_time() and isinstance(fruit, Apple):
yumminess = fruit.size * fruit.ripeness ** 2
happiness += hunger * yumminess
return happiness
このコードを読むときは、どこでも使用または変更できるため、関数の最初から最後まで変数を念頭に置く必要があります。
def assess_fruit(self, fruit):
happiness = 0
some_other_code()
# work out some things
do_some_other_things()
if is_snack_time() and isinstance(fruit, Apple):
hunger = time_since_breakfast() / size_of_breakfast()
yumminess = fruit.size * fruit.ripeness ** 2
happiness += hunger * yumminess
return happiness
使用するスコープに移動するようにリファクタリングすると、この問題を解決できます。今、私たちは適切な範囲内で検討するために飢餓を必要とするだけです。