code-refactor-master について
動作を変えずにコード構造を改善したい開発者向けのリファクタリングスキルです。コードスメルや一般的な手法、Java・Python の実践例、テストを伴う段階的な手順を扱います。
主な機能
- 01一般的なコードスメルの特定
- 02メソッドとクラスのリファクタリング
- 03Java 固有のリファクタリング手法
- 04Python 固有のリファクタリング手法
- 05テストを使った段階的な変更
- 06動作を保った性能改善
活用シーン
- 01重複コードと複雑さの削減
- 02既存コードの保守性向上
- 03Java または Python 解法の整理
- 04アルゴリズム実装の最適化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yennanliu/ cs_basics --skill code-refactor-masterインストール後、Claude Code で/code-refactor-masterを入力して読み込まれたか確認します。
詳細
コードリファクタリングマスター
このスキルを使う場面
次のような場合にこのスキルを使います。
- コードの品質を高めるために既存のコードをリファクタリングする
- コードの不吉な臭いを取り除く
- コードの可読性と保守性を向上させる
- 振る舞いを変えずにパフォーマンスを最適化する
- デザインパターンを適用する
- 複雑さを軽減する
- 技術的負債を解消する
リファクタリングの原則
1. 基本ルール
黄金律: 外部の振る舞いを変えずに、内部構造を改善するコード変更を行います。
重要な原則:
- 一度にリファクタリングするのは1つだけです
- リファクタリングのたびにテストを実行します
- 明確なメッセージを付けてこまめにコミットします
- リファクタリングと機能開発を分けます
- API を明示的に変更する場合を除き、後方互換性を維持します
リファクタリングを検討すべき兆候:
- コードの重複(DRY 原則)
- 長いメソッド(20~30行超)
- 大きなクラス(300~500行超)
- 長いパラメーターリスト(3~4個超)
- 深くネストした条件分岐(3階層超)
- コードの内容を説明するコメント(コード自体で内容が明確であるべきです)
2. よくあるコードの不吉な臭い
肥大化:
- 長いメソッド
- 大きなクラス
- プリミティブ型への執着
- 長いパラメーターリスト
- データの塊
オブジェクト指向の濫用:
- switch 文(ポリモーフィズムを検討します)
- 一時フィールド
- 拒否された遺産
- インターフェースが異なる代替クラス
変更の妨げ:
- 変更の発散(1つのクラスが多くの理由で変更される)
- ショットガン手術(1つの変更に多くの小さな変更が必要になる)
- 並列継承階層
不要なもの:
- コメント(コード自体で内容が明確であるべき箇所)
- 重複コード
- 怠惰なクラス
- デッドコード
- 投機的一般化
結合:
- 機能への執着
- 不適切な親密さ
- メッセージチェーン
- 仲介者
3. リファクタリングカタログ
メソッドレベルのリファクタリング:
メソッドの抽出
// Before
void printOwing() {
printBanner();
// print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
// After
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
メソッドのインライン化
// Before - method too simple
int getRating() {
return moreThanFiveLateDeliveries() ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return numberOfLateDeliveries > 5;
}
// After
int getRating() {
return numberOfLateDeliveries > 5 ? 2 : 1;
}
一時変数をクエリに置き換える
// Before
double calculateTotal() {
double basePrice = quantity * itemPrice;
if (basePrice > 1000) {
return basePrice * 0.95;
}
return basePrice * 0.98;
}
// After
double calculateTotal() {
if (basePrice() > 1000) {
return basePrice() * 0.95;
}
return basePrice() * 0.98;
}
double basePrice() {
return quantity * itemPrice;
}
変数レベルのリファクタリング:
変数名の変更
# Before
d = 10 # elapsed time in days
# After
elapsed_time_in_days = 10
一時変数の分割
// Before
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);
// After
double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);
クラスレベルのリファクタリング:
クラスの抽出
// Before
class Person {
String name;
String officeAreaCode;
String officeNumber;
String getTelephoneNumber() {
return "(" + officeAreaCode + ") " + officeNumber;
}
}
// After
class Person {
String name;
TelephoneNumber officeTelephone = new TelephoneNumber();
String getTelephoneNumber() {
return officeTelephone.getTelephoneNumber();
}
}
class TelephoneNumber {
String areaCode;
String number;
String getTelephoneNumber() {
return "(" + areaCode + ") " + number;
}
}
条件分岐をポリモーフィズムに置き換える
# Before
class Bird:
def get_speed(self):
if self.type == "EUROPEAN":
return self.get_base_speed()
elif self.type == "AFRICAN":
return self.get_base_speed() - self.get_load_factor()
elif self.type == "NORWEGIAN_BLUE":
return 0 if self.is_nailed else self.get_base_speed()
# After
class Bird:
def get_speed(self):
pass # Abstract
class European(Bird):
def get_speed(self):
return self.get_base_speed()
class African(Bird):
def get_speed(self):
return self.get_base_speed() - self.get_load_factor()
class NorwegianBlue(Bird):
def get_speed(self):
return 0 if self.is_nailed else self.get_base_speed()
4. Java 固有のリファクタリング
最新の Java 機能を使う:
匿名クラスをラムダ式に置き換える
// Before
list.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// After
list.sort((a, b) -> a.compareTo(b));
// Or even better
list.sort(String::compareTo);
Streams API を使う
// Before
List<String> result = new ArrayList<>();
for (String s : list) {
if (s.length() > 3) {
result.add(s.toUpperCase());
}
}
// After
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
文字列の連結を置き換える
// Before - inefficient in loops
String result = "";
for (String s : list) {
result += s + ", ";
}
// After
StringBuilder result = new StringBuilder();
for (String s : list) {
result.append(s).append(", ");
}
// Or better
String result = String.join(", ", list);
Optional を使う
// Before
public User findUser(int id) {
User user = database.getUser(id);
return user != null ? user : new User();
}
// After
public Optional<User> findUser(int id) {
return Optional.ofNullable(database.getUser(id));
}
型コードを Enum に置き換える
// Before
public static final int TYPE_A = 1;
public static final int TYPE_B = 2;
// After
public enum Type {
A, B
}
5. Python 固有のリファクタリング
リスト内包表記を使う
# Before
result = []
for item in items:
if item > 0:
result.append(item * 2)
# After
result = [item * 2 for item in items if item > 0]
Collections モジュールを使う
# Before
counts = {}
for item in items:
if item in counts:
counts[item] += 1
else:
counts[item] = 1
# After
from collections import Counter
counts = Counter(items)
コンテキストマネージャーを使う
# Before
file = open('file.txt')
data = file.read()
file.close()
# After
with open('file.txt') as file:
data = file.read()
f-string を使う
# Before
message = "Hello, %s! You are %d years old." % (name, age)
# After
message = f"Hello, {name}! You are {age} years old."
型ヒントを使う
# Before
def process(data):
return [x * 2 for x in data]
# After
def process(data: list[int]) -> list[int]:
return [x * 2 for x in data]
データクラスを使う
# Before
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
# After
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
6. クリーンコードの原則
命名:
// Bad
int d; // elapsed time in days
// Good
int elapsedTimeInDays;
// Bad
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x : theList) {
if (x[0] == 4) {
list1.add(x);
}
}
return list1;
}
// Good
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell : gameBoard) {
if (cell.isFlagged()) {
flaggedCells.add(cell);
}
}
return flaggedCells;
}
関数:
- 小さくする(最大20~30行)
- 1つのことだけを行う
- 抽象化のレベルを1つにする
- 説明的な名前を付ける
- 引数を少なくする(0~3個が理想)
コメント:
// Bad - comment explains what code does
// Check to see if employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
// Good - code is self-explanatory
if (employee.isEligibleForFullBenefits())
7. パフォーマンスのリファクタリング
アルゴリズムの最適化:
# Before - O(n²)
def has_duplicates(arr):
for i in range(len(arr)):
for j in range(i + 1, len(arr)):
if arr[i] == arr[j]:
return True
return False
# After - O(n)
def has_duplicates(arr):
return len(arr) != len(set(arr))
遅延評価:
// Before - computes all values
List<Integer> results = list.stream()
.map(this::expensiveOperation)
.collect(Collectors.toList());
return results.get(0);
// After - computes only what's needed
return list.stream()
.map(this::expensiveOperation)
.findFirst()
.orElse(null);
メモ化:
# Before
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# After
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
8. リファクタリングのワークフロー
手順:
- 特定: コードの不吉な臭いや改善の機会を見つけます
- 計画: 適用するリファクタリングを決めます
- テスト: テストが存在し、合格することを確認します
- リファクタリング: 小さな変更を1つ行います
- テスト: もう一度テストを実行します
- コミット: 動作する状態を保存します
- 繰り返し: 次のリファクタリングに進みます
ワークフローの例:
# 1. Create feature branch
git checkout -b refactor/improve-user-service
# 2. Identify issue (e.g., long method)
# Read code, find UserService.processUser() is 150 lines
# 3. Run existing tests
mvn test # or pytest
# 4. Extract method
# Break processUser into smaller methods
# 5. Run tests
mvn test # Ensure still passing
# 6. Commit
git commit -m "refactor: extract validateUser method"
# 7. Continue
# Extract next method, repeat
9. リファクタリングのチェックリスト
リファクタリングの前:
- テストが存在し、合格する
- コードの振る舞いを理解している
- 明確な目標がある
- 適用するリファクタリングが分かっている
リファクタリング中:
- 小さく段階的な変更を行う
- 変更のたびにテストを実行する
- 常にコードが動作する状態を保つ
- こまめにコミットする
リファクタリングの後:
- すべてのテストに合格する
- コードの可読性が向上している
- 複雑さが軽減されている
- 振る舞いに変更がない
- 必要に応じてドキュメントを更新している
10. LeetCode 固有のリファクタリング
総当たりを最適化する:
// Before - Brute force O(n²)
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[]{};
}
// After - Hash map O(n)
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[]{};
}
ヘルパーメソッドを抽出する:
# Before - monolithic
def solve(self, grid):
# 50 lines of code mixing concerns
# After - modular
def solve(self, grid):
if not self.is_valid(grid):
return []
processed = self.preprocess(grid)
result = self.compute(processed)
return self.format_output(result)
def is_valid(self, grid):
# validation logic
def preprocess(self, grid):
# preprocessing logic
def compute(self, data):
# core algorithm
def format_output(self, result):
# formatting logic
リファクタリングのアンチパターン
避けるべきこと:
- テストなしでリファクタリングする
- リファクタリング中に動作を変更する
- 複数の変更を一度に行う
- リファクタリングと機能追加を同時に行う
- シンプルなコードを過度に設計する
- 早まった最適化を行う
プロジェクトのコンテキスト
CS_basics リポジトリの場合:
leetcode_java/とleetcode_python/の解答をリファクタリングする- 言語ごとの実装間で一貫性を保つ
- アルゴリズムの説明を最新の状態に保つ
- プロジェクトの規約に従う
- リファクタリングしたコードを入力例でテストする
- 計算量の改善を記録する
ツールとリソース
IDE のリファクタリングツール:
- IntelliJ IDEA:リファクタリングメニュー(Ctrl+Alt+Shift+T)
- VS Code:Python のリファクタリング拡張機能
- PyCharm:リファクタリングアクション
利用可能な場合は、自動リファクタリングを使用します:
- 名前の変更:プロジェクト全体で安全に名前を変更します
- メソッド/変数の抽出:自動で抽出します
- インライン化:プレビュー付きで安全にインライン化します
- 移動:パッケージ/モジュールを再構成します
コメント
Markdown 対応