実用的なまとめ | 反復要件におけるマイクロコードの再構築を覚えておいてください

皆さんこんにちは。DingTalk ビジネス プラットフォームのフロントエンド テクノロジー エンジニアの Dan Dan です。以前は、DingTalk の出席、ログ記録、承認、オープン プラットフォーム、ワークベンチなどの主要なビジネスを経験した後、より技術的なアーキテクチャやビジネス思考を共有していましたが、今回はプログラマーの日常的なニーズをミクロレベルで記録しただけです。 . 再構築のプロセス。

需要の背景

ページは左のとおりですが、赤枠内の部分を赤矢印の位置に移動させると右のような効果が得られます。

ニーズ評価

元のページのコードを見る前は、これは小さな要件だと思うかもしれません。ページ上のコンポーネントが移動できたら便利だと思いませんか? 要件はすぐに完了し、ビジネス側は満足し、私たち全員に明るい未来が与えられます。コードを読んだ後、私は突然素朴な気持ちになりました。それは思っているほど単純ではなく、間違いを犯しやすいものです。以下では理由を分かりやすく説明するために略語を使用していますが、ビジネス上の意味を理解する必要はありません。

この要件を満たすには、次の 3 つのことを行う必要があります。

1. 目的の場所を見つける

2. ターゲットコンポーネントを見つける

3. 移動

まず現在のページ構造を分析しましょう: ページ ページには 2 つのコンポーネント A と B が含まれています。コンポーネント A には 2 つのサブコンポーネント a1 と a2 があります。ターゲットの位置は a1 と a2 の間にあります。ターゲットの位置は非常に明確です。問題はBにあります。b1 の移動する部分はコンポーネント B 内にありますが、b1 はコンポーネントではなく、B 内のコードの束です。b1 ロジックには AR と HR の 2 つのコンポーネントがあり、条件によってどちらかが表示される場合と表示されない場合があります。そして、b1 は B コンテキストの 10 個の属性に依存します。ターゲット b1 がコンポーネントでない場合、動きの複雑さは UPUP になります。

パート b1 のコード表現:

.../** b1部分的代码 */let hRecord = null;const hasSupplySuite = hasSupplySuite();if (type === TypeENUM.REPAIR_CHECK && data && !hasSupplySuite) {hRecord = (<ARecordid={originatorInfo.workNo}pId={pId}/>);} else if (hasSupplySuite || type > 1) {hRecord = (<HRecordpId={pId}id={originatorInfo.workNo}count={currentCount}name={schema.title}username={originatorInfo.name}isLeave={type === TypeEnum.LEAVE}code={formData.code.value}/>);}if (!isUserInList() || location.href.indexOf('list') !== -1) {hRecord = null;}...

b1 のロジックをコンポーネント B からコンポーネント A に移植する場合は、次の 10 個のパラメータに注意する必要があります。形状の異なる10個の箱を窓(コンポーネントのAPI)を通して部屋Bから部屋Aに移動するようなものですが、移動ツールがないので1つずつ移動するしかありません。A さんの窓と B さんの窓は大きさや形が異なるため、一部が入らない場合があり、A さんが窓を拡大する必要があります。大根を引き抜いて泥を出す作業は、失敗せずに行うのが難しい作業です。

コンポーネント A とコンポーネント B にはすでに 30 を超える API が存在しており、長年にわたって次々と要件が重ね合わされていると推定されますが、これまでのようにこの要件に対してさらに多くの API を重ね合わせる必要がある場合は、漏れがないよう注意する必要があります。衝突。

このアプローチは非常に見苦しく、将来の拡張には役に立たず、むしろ穴をどんどん深く掘り下げてしまいます。復興の心臓部が動き出す。

もちろん、リファクタリングを選択するかどうかに直面した場合、次のような多くのアイデアが考えられます。

1. リファクタリングのメリットは通常、将来実現しますが、行う必要がありますか?

2. リファクタリングには多くの時間がかかりますが、コードの読みやすさを向上させることは、他の人にとっての落とし穴を減らすだけであり、私にとっては実際のメリットがない可能性があります。

3. リファクタリングによってバグが発生したり、不要なトラブルが発生する可能性もありますが、そのリスクを負わなければなりませんか?

これらも、古いアプリケーション コードが使用できなくなる理由の 1 つである可能性があります。しかし:

1. リファクタリングがなければ、コード内の異臭がますますうんざりし、開発者はゴミだらけの部屋に入ったようなものになります。

2. リファクタリングされていない場合、他の人が将来コードのこの部分を変更する場合に再度理解する必要があり、チームの全体的な研究開発コストが増加します。

3. 再構築しないと、心の中で非常に罪悪感を感じるでしょう。

リファクタリングの目的

時間コストを抑え、再構築の範囲を最小限に抑えるためには、要件を中心に今回の目的を明確にする必要があります。

目標: このリクエストを簡単に完了できるようにします。

これには次の 3 つのことが必要です。

1. パーツ b1 を独自にモバイル対応コンポーネントにし、モバイルのコストを削減します。

2. どこにいてもコードの読みやすさを向上させ、コードを理解するコストを削減します。

3. 本来の機能は変わりません。

リファクタリングプロセス

古いコードをリファクタリングするときは特に注意してください。リファクタリング ステップを設計します。各ステップは独立した小さな変更であるため、各ステップをテストし、各ステップのコードを使用してバグの可能性を減らすことができます。

パート b1 のコード ロジックを分析します。これは、AR および HR と呼ばれる 2 つのコンポーネント ARecord と HRecord を参照します。HR は独立したコンポーネントです。AR はコンポーネント B に記述されます。b1 はコンポーネントにパッケージ化されるため、最初に AR を独立してパッケージ化する必要があります。

1. AR コンポーネントを B に接続するのではなく独立させます。

まず、AR コードを B から移動し、少し変更して API をカプセル化し、型定義を追加します。context を使用して、コンポーネント ホストを参照するコンテキスト情報を表します。たとえば、埋め込みページのソース情報をこのオブジェクトに入れることができます。ComponentProps はコンポーネント データを表します。この手順は比較的簡単です。

元のコードは B にあります。

class ARecord extends React.Component {openARecord = () => {const openUrl = `${getBaseUrl()}#/list`;openLink(openUrl);}render() {return (<div className="content" onClick={this.openARecord}>      ...</div>);}}

新しいコード:

// 从AFlow中将ARecord迁移出来稍加改造// 组件接口定义interface IARecordProps {  className?: string;  style?: object;context: IContextModel;// 本组件的数据componentProps: {id: string;pId: string;  }}
export default function ARecord(props: IARecordProps) {const {    className,    style = {},    context = {},componentProps,  } = props;
const {    utSpace,  } = context;
const {    id,    pId,  } = componentProps;
const openARecord = (id: string, pId: string) => {const openUrl = `${getBaseUrl()}#/arecordlist`;openLink(openUrl);};
return <div className="content"     onClick={() => openARecord(id, pId)}  >  ...  </div>;}

2. ページ機能テスト

ページ内の B コンポーネントに context 属性を追加し、B コンポーネント内の元の場所にある新しい AR コンポーネントを参照し、AR API に従ってデータを転送し、ページ ロジックをテストします。テストに合格しました。

3. AR と HR の両方に、移動して b1 コンポーネントのパッケージ化を開始する条件が揃っています。

このステップは少し注意が必要です。主な理由は、b1 コードと B コンポーネントが深く結合されているためです。それに依存するプロパティとメソッドがあります。元々使用されていた属性はさておき、まず全体像から始めましょう。まず、新しいコンポーネントの外部 API を定義します。これは、変更されていない元のコンポーネントに対応します。元のコードには型定義がないため、元の属性がどの型であるかを判断するのに時間がかかります。

型定義を追加するだけです。

API 設計は、コンポーネントをカプセル化するときに非常に重要です。私は、コンポーネントの API を 2 つのオブジェクト (それぞれホスト コンテキストとコンポーネント データを表す context とComponentProps) として設計するなど、変更が容易な部分をカプセル化することに慣れています。ホストはシンプルで使いやすいです。後で新しい属性を追加する場合は、データ処理ロジックを追加するだけです。テンプレート部分のコードを変更する必要はありません。コードは簡潔に見え、修正コストが低くなります。

// b1组件数据模型定义interface IHRecordSummaryData {hasSupplySuite: ()=>boolean;id: string;pId: string;type: TypeEnum;data: {};count: number;name: string;username: string;code: string;isUserInList: ()=>boolean;}// b1组件数据API定义interface IHRecordSummaryProps {  className?: string;  style?: object;context: IContextModel;// 本组件的数据componentProps: IHRecordSummaryData;}

元のロジックの if elseif ステートメントを、単純で理解しやすいガード ステートメントに変更すると便利です。他の内部ロジックはそのままにしておきます。

export default function HRecordSummary(props: IHRecordSummaryProps) {  const {    context,    componentProps,  } = props;
  const {    id,    pId,  } = componentProps || {};
  const getComponentNode = (componentProps: IHRecordSummaryData) => {    const {      hasSupplySuite,      id,      pId,      type,      data,      count,      name,      username,      code,      isUserInList,    } = componentProps || {};
    const aRecordData = {      id,      pId,    }
    if (type === TypeENUM.REPAIR_CHECK && data && !hasSupplySuite) {      return  <ARecord        context={context}        componentProps={aRecordData}      />;    }         if (hasSupplySuite || type > 1) {      return  <HRecord        pId={pId}        id={id}        count={count}        name={name}        username={username}        isLeave={type === TypeEnum.LEAVE}        code={code}      />    }      if (!isUserInList || location.href.indexOf('list') !== -1) {      return null;    }  };
  return <div>    {getComponentNode(componentProps)}  </div>;}

4. ページ機能のテスト

B の元の場所にある b1 コンポーネントを参照し、コンポーネントのデータ型定義に従って、componentProps データをアセンブルしてテストします。テストに合格しました。

B は、b1 に渡されたデータをカプセル化します。

...    const context = {      utSpace: context?.utSpace,    };    const hRecordSummaryData = {      id: originatorInfo?.workNo,      pId: pId,      hasSupplySuite: hasSupplySuite(),      type: type,      data: data,      count: currentCount,      name: schema?.title,      username: originatorInfo?.name,      code: formData?.code?.value,      isUserInList: isUserInList(),    };...
return (  ...    <HRecordSummary      context={context}      componentProps={hRecordSummaryData}    >    </HRecordSummary>  ...)

5. b1 コンポーネントは API 設計に苦労しています

転送される元の依存属性は合計 10 個あり、元の 10 個の属性は 10 個の API を介して Page から B に渡され、B から b1 に渡されるため、オブジェクトにカプセル化する方が便利です。テンプレート部分のコードを変更することなく、拡張フィールドのロジックを追加するだけで済みます。オブジェクトにカプセル化するのが適切かどうかは、これら 10 個が他の場所で使用されているかどうかによって決まります。B のコードを検索すると、10 個の属性のうち 1 つだけが他のロジックで使用され、残りは b1 でのみ使用されます。最初にカプセル化できます。

まず、B の 10 個の API を、タイプがオブジェクト、合計 10 個の属性を持つ 1 つの API にマージし、このオブジェクトを B から取得して b1 に渡します。

次に、このオブジェクトを Page でアセンブルして B に渡します。同時に、B の元の 9 つの余分な API を削除します。他のロジックで使用される 1 つは忘れずに保持してください。B では、1 つの新しい API が追加され、9 つが削除され、B の API は 37 から 29 に減り、コードもすっきりしました。

6. ページ機能のテスト

Page から B、次に b1 に渡された 10 個の属性を 1 つのオブジェクトに変更してテストします。テストに合格しました。

7. b1 コンポーネントの型定義について混乱している

b1 の元の属性に 2 つのメソッドが渡されており、それをそのまま移行しました。コードを見ると、これら 2 つのメソッドは現在のコンテキストに関連していません。メソッドの結果のブール値のみを渡すように変更できます。メソッドを渡す必要はありません。

8. ページ機能のテスト

Page から B、次に b1 に渡される 2 つのメソッドをブール値に変更してテストします。テストに合格しました。

9. b1コンポーネントタイプ定義のデータ

コンポーネントに必要な API のみを設計し、冗長なデータは転送しません。

コードを見てみると、同様に属性の一つであるdataも条件判定にのみ使用されており、その存在のみが条件となっていますが、データ量自体は決して少なくありません。実際にはブール値に変更できます。

10. ページ機能テスト

Page から B に渡され、次に b1 に渡されたデータをブール値に変更してテストします。テストに合格しました。

11. 現時点では、コンポーネントの高速移動の条件が満たされています。

この時点で、コードにはコンポーネントを迅速に移動するための条件がすでに備わっています。後々の操作が楽になりますよ。同じコンポーネントのデータ型定義を ApproveHead に追加し、データを ApprovePage、ApproveFlow、ApproveHead に渡し、コンポーネント参照を ApproveFlow から ApproveHead に変更します。要件は満たされています。

12. ページ機能テスト

ページ関数回帰テスト、テストは合格しました。

要約する

優れたコードは常に目を楽しませてくれますし、読者は作者と簡単に合意に達することができます。すべての要件は、古いコードを改善する機会となります。理解しやすく、変更しやすいコードを作成するよう努め、各要件の反復にリファクタリングを組み込んで、時間の経過とともにコードが破損するのを防ぎます。

ただし、需要の反復にはサイクルがあり、毎回完全に中断することはできません。それぞれのリファクタリングの目的を決定し、適度に停止することで、ビジネスのリズムを尊重し、最適化することができます。時間が経つにつれて、状況は変化します。

「どんな愚か者でもコンピュータが理解できるプログラムを書くことはできる。人間が容易に理解できるプログラムだけが優れたプログラマーである。」 - マーティン・フラワー

クリックして今すぐクラウド製品を無料で試し、クラウドでの実践的な取り組みを始めましょう!

元のリンク

この記事は Alibaba Cloud のオリジナル コンテンツであり、許可なく複製することはできません。

Lei Jun氏はXiaomiのThePaper OSの完全なシステムアーキテクチャを発表し、最下層が完全に再構築されたと述べ、 Yuque氏は10月23日に障害の原因と修復プロセスを発表 Microsoft CEOのナデラ氏「Windows Phoneとモバイル事業を放棄したのは間違った決断だった」 . Java 11 と Java 17 の使用率は両方とも Java 8 を上回りました. Hugging Face は Yuque へのアクセスを制限されました. ネットワーク障害は約 10 時間続きましたが、現在は通常に戻っています. 国家データ局が Oracle を正式に発表しました. Visual Studio 用の Java 開発拡張機能を開始しましたCode.Musk : Wikipedia が「Weiji Encyclopedia」に名前変更されたら 10 億寄付 USDMySQL 8.2.0 GA
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/yunqi/blog/10123316