blog

リファクタリング - TypeScriptをコーディングするすてきな方法編

このタイトルを見て思い浮かぶ本が2冊あります。1つは「リファクタリング - 既存のコードの設計を改善する」、もう1つは「クリーンコードの道」です。これらは本当に素晴らしい本ですが、残念ながら提供されて...

Jul 8, 2020 · 7 min. read
シェア

このタイトルを見て思い浮かぶのは、「リファクタリング - 既存コードの設計を改善する 」と「 クリーンコードの道 」の2冊でしょう。これらは本当に素晴らしい本ですが、残念ながら提供されているコードはJavaです。リファクタリング-既存コードの設計を改善する 』の第2版ではJavaScript版が提供されており、フロントエンドの学生が読むにはすでにとても便利なのですが、TypeScripがこれほどホットな今日、TS版がないのはいつも残念です。 そこで、老元は毎日時間をかけて、彼の10年の経験と組み合わせた非常に古典的なケースをTSリライトの一部にまとめ、皆さんのお供をしたいと思います。皆さんの技術的な成長の道のりに寄り添い、次のような方向から皆さんと分かち合いたいと思います:

  • npmの効果的なツールで悪趣味なコードを校正
  • Jestなどのフロントエンドテストツールを使用したテストの構築
  • TypeScriptを使用して、関数やデータを再編成します。
  • 関数式、関数呼び出しの簡素化
  • 汎化への対応、リファクタリングの実践、リファクタリングに基づくCLIツール
  • ....

皆さんも私と一緒に、TypeScriptをきちんと書く旅に出かけましょう:

リファクタリングとは ?

単純に理解すると、理解しやすくし、修正コストを削減するために、観察可能な動作を変えずにソフトウェアの内部構造を改善することです。

リファクタリングを始めるタイミングは?

  • 既存のコードでは新機能を簡単に追加できない場合の繰り返し作業。

  • バグフィックス時のロジックのトラブルシューティングが困難。

これは、達成すべき目標とタスクのリストです。

  • Add Parameter
  • Change Bidirectional Association to Unidirectional
  • Change Unidirectional Association to Bidirectional
  • Change Reference to Value
  • Change Value to Reference
  • Collapse Hierarchy
  • 条件式の統合
  • Consolidate Duplicate Conditional Fragments
  • Convert Procedural Design to Objects
  • Decompose Conditional
  • Duplicate Observed Data
  • Encapsulate Collection
  • Encapsulate Downcast
  • Encapsulate Field
  • Extract Class
  • Extract Hierarchy
  • Extract Interface
  • Extract Method
  • Extract Subclass
  • Extract Superclass
  • Form Template Method
  • Hide Delegate
  • Hide Method
  • Inline Class
  • Inline Method
  • Inline Temp
  • Introduce Assertion
  • Introduce Explaining Variable
  • Introduce Foreign Method
  • Introduce Local Extension
  • Introduce Null Object
  • Introduce Parameter Object
  • Move Field
  • Move Method
  • Parameterize Method
  • Preserve Whole Object
  • Pull Up Constructor Body
  • Pull Up Field
  • Pull Up Method
  • Push Down Field
  • Push Down Method
  • Remove Assignments to Parameters
  • Remove Control Flag
  • Remove Middle Man
  • Remove Parameter
  • Remove Setting Method
  • Rename Method
  • Replace Array with Object
  • Replace Conditional with Polymorphism
  • Replace Constructor with Factory Method
  • Replace Data Value with Object
  • 委任を継承に置き換える
  • Replace Error Code with Exception
  • Replace Exception with Test
  • Replace Inheritance with Delegation
  • Replace Magic Number with Symbolic Constant
  • Replace Method with Method Object
  • Replace Nested Conditional with Guard Clauses
  • Replace Parameter with Explicit Methods
  • Replace Parameter with Methods
  • Replace Record with Data Class
  • Replace Subclass with Fields
  • Replace Temp with Query
  • Replace Type Code with Class
  • Replace Type Code with State/Strategy
  • Replace Type Code with Subclasses
  • Self Encapsulate Field
  • Separate Domain from Presentation
  • Separate Query from Modifier
  • Split Temporary Variable
  • Substitute Algorithm
  • 遺産相続

Add Parameter

説明 : 関数が引数なしで問題を解決できるなら、それはもちろん素晴らしいことです。しかし、日常的な開発では、関数に引数を追加する必要がよくあります。

動機 : このリファクタリングを使う動機は単純で、関数を追加する必要があり、変更された関数は過去になかった情報を必要とします。

// 
    1.  関数パラメータ
function createMenu(title, body, buttonText, cancellable) {
 ...
}
// 
    2.  2つ以上のパラメータ オブジェクトを使って一様に渡す
interface IMenuConfig {
 title?: string;
 body?: string;
 buttonText?: string;
 cancellable?: boolean;
}
const menuConfig: IMenuConfig = {
 title: 'Foo',
 body: 'Bar',
 buttonText: 'Baz',
 cancellable: true,
};
function createMenu(menuConfig: IMenuConfig) {}

上記の複数のパラメータを1つにまとめる問題を解決し、パラメータの長さを短縮し、後でまた、ステップバイステップで紹介します。

Duplicated Code(重複コード)

Long Method

Large Class

Long Parameter List

Divergent Change ...

function add(...rest: number[]): number;
function add(...rest: string[]): string;
function add(...rest: any[]) {
 let first = rest[0];
 if (typeof first === 'number') {
 return rest.reduce((pre, cur) => pre + cur);
 }
 if (typeof first === 'string') {
 return rest.join('');
 }
}
console.log(add(1, 2))
console.log(add('a', 'b', 'c'));
//しかし、上のコードはもっと簡潔にできたはずだ
interface ConfigFn {
 <T>(value: T): void;
}
var getData2: ConfigFn = function <T>(value: T): T {
 return value;
};
getData2<string>('老元');

もちろん、関数には他にも多くのリファクタリングルールがあり、今後徐々に浸透させていく予定です。

Change Bidirectional Association to Unidirectional

説明:2つのクラスの間に双方向の関連がありますが、一方のクラスはもう一方のクラスの特性を必要としません。

動機 : 双方向の関連付けは便利ですが、その代償として双方向のリンクを維持しなければなりません。大量の双方向リンクは、死んでもヒープに存在するオブジェクトによるメモリリークを引き起こしやすくなります。さらに、双方向リンクの間には依存関係があり、これらがnodeで実行されるなど、webpackによってパッケージ化されていない2つの別々のファイルである場合、これはクロスファイル依存です。クロスファイルの依存関係は結合システムを作成し、小さな変更のようなどんな小さな変更でも多くの予測不可能な結果を引き起こします。そのため、本当に双方向の相関が必要な場合にのみ使用してください。

// 割引商品の注文カテゴリー
class Order {
 private _customer: Customer;
 public getCustomer(): Customer {
 return this._customer;
 }
 public setCustomer(customerName: string, customer: Customer): void {
 const { _customer } = this;
 if (_customer) {
 // 値引きされた商品は1回の注文でしか購入できない。 価格はリアルタイムで計算される。
 customer.friendOrders.delete(customerName);
 } else {
 this._customer = customer;
 customer.friendOrders.set(customerName, this);
 }
 }
 public disCountedPrice(): number {
 return Math.random() * this._customer.getDiscount();
 }
}
class Customer {
 private _orders = new Map<string, Order>();
 public addOrder(orderName: string, order: Order): void {
 order.setCustomer(orderName, this);
 }
 public getDiscount(): number {
 //ユーザーレベルに応じた割引以外の価格を手に入れる
 return 0.8;
 }
 public getPriceFor(order: Order): number {
 return order.disCountedPrice();
 }
 get friendOrders() {
 return this._orders;
 }
}
const john = new Customer();
const basket = new Order();
john.addOrder('shoes', basket);
const priceShoes = john.getPriceFor(basket);
console.log('
    1. ', priceShoes);
john.addOrder('clothes', basket);
const priceClothes = john.getPriceFor(basket);
console.log('
    2. ', priceClothes);
console.log('コンサの総決算, priceShoes + priceClothes);
/* 
    1. 上の段落の核心は、最終製品の価格を計算することである
* 
    2. アイテムの価格を取得するのは2つのステップに分かれている。
* 
    3. その後、アイテム自体の割引のランダムな数を得た 最終的に一緒に掛ける
* コアコードはaddOrderとgiveOrderである。_customer  
 本当に全く必要ない
*/

考えてみれば、顧客は注文の前に来るので、リンクを外すことは完全に可能です。注文は割引情報を_customerに依存しているので:

//Orderを修正し、customerを外部から渡して2つのクラスを完全に分離する。
public disCountedPrice(customer: Customer): number {
 return Math.random() * customer.getDiscount();
}
//または、カスタマーを修正する
public getPriceFor(order: Order): number {
 return order.disCountedPrice(this);
}

最後に、積極的にCustomerのインスタンスを探すようにgetCustomerを修正する暴力的な手段があります。これは一方向性を維持し、最終的にsetCustomerを置くと実際に削除されます。その核心は、逆ポインタを削除することです。

Change Unidirectional Association to Bidirectional

これは、上記ののちょうど反対です。つまり、2つのクラスがお互いを拘束する必要がある場合、クラスのクラスによって制御される必要がある場合、ちょうど一方向の関連付けから双方向の関連付けに一致するように、vueの双方向のデータバインディングは、ウォッチャーとデータの関係の間にちょうど最適です。

Read next

Javaアノテーションの詳細

まず、注釈1.1とは何ですか:基本的な概念は、JDK5.0の新技術の役割から導入されるプログラム自体ではなく、プログラムの説明を行うことができます大きな違いはありません)他の手順することができます

Jul 8, 2020 · 2 min read