실제 요약 | 반복 요구 사항에서 마이크로 코드 재구성을 기억하세요

안녕하세요 여러분, 저는 DingTalk 비즈니스 플랫폼의 프론트엔드 기술 엔지니어 Dan Dan입니다. 예전에는 DingTalk의 출석, 로깅, 승인, 오픈 플랫폼, 워크벤치 등 핵심 사업을 경험한 후 공유는 기술적인 아키텍처나 비즈니스적 사고가 컸는데, 이번에는 프로그래머의 일상적인 요구 사항을 미시적으로 기록했습니다. .재건 과정.

수요 배경

페이지는 왼쪽과 같으며, 오른쪽과 같은 효과를 얻으려면 빨간색 상자 안의 부분을 빨간색 화살표가 가리키는 위치로 이동해야 합니다.

평가 필요

원본 페이지 코드를 보기 전에는 이것이 작은 요구 사항이라고 생각할 수 있습니다. 페이지의 구성 요소를 이동하는 것이 좋지 않을까요? 요구 사항이 신속하게 완료되고 비즈니스 측면이 만족되며 우리 모두의 미래는 밝습니다. 코드를 읽은 후 갑자기 순진함을 느꼈습니다. 생각보다 간단하지 않고, 실수하기도 쉽습니다. 그 이유를 명확하게 설명하기 위해 아래에는 약어를 사용하였으며, 업무상 의미를 이해할 필요는 없습니다.

이 요구 사항을 충족하려면 다음 세 가지 작업을 수행해야 합니다.

1. 대상 위치 찾기

2. 대상 구성요소 찾기

3. 이동

먼저 현재 페이지 구조를 분석해 보겠습니다. 페이지 페이지에는 두 개의 구성 요소 A와 B가 포함되어 있습니다. 구성 요소 A에는 두 개의 하위 구성 요소인 a1과 a2가 있습니다. 대상 위치는 a1과 a2 사이이며 대상 위치는 매우 명확합니다. 문제는 B에 있다. b1의 이동될 부분은 컴포넌트 B 내에 있지만, b1은 컴포넌트가 아니고 B 내부의 코드 묶음입니다. b1 로직에는 AR과 HR이라는 두 가지 구성요소가 있는데, 그 중 하나는 서로 다른 조건에 따라 표시되거나 표시되지 않을 수 있습니다. 그리고 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방으로 옮기는 것과 같습니다. 운반 도구가 없어 하나씩만 옮길 수 있습니다. A의 창문과 B의 창문은 크기와 모양이 다르고 일부는 맞지 않아 A가 창문을 확장해야 할 수도 있습니다. 무를 뽑고 진흙을 꺼내는 일은 실수 없이는 하기 어려운 과정이다.

구성 요소 A와 B에 대한 API는 이미 30개가 넘으며, 수년에 걸쳐 요구 사항이 차례로 중첩된 것으로 추정됩니다. 평소대로 이 요구 사항에 대해 더 많은 API를 중첩해야 하는 경우 누락이 발생하지 않도록 주의해야 합니다. 갈등.

이러한 접근 방식은 보기에도 좋지 않고 향후 확장에도 도움이 되지 않으며 오히려 구멍을 점점 더 깊게 파고 있습니다. 재건축의 심장은 움직일 준비가 되어 있습니다.

물론 리팩터링을 선택할지 여부에 직면했을 때 다음과 같은 많은 아이디어가 있을 것입니다.

1. 리팩토링의 이점은 대개 미래에 실현되는데, 지금 해야 합니까 ?

2. 리팩토링은 나에게 많은 시간이 소요되지만, 코드의 가독성을 높이는 것은 다른 사람들의 함정을 줄이는 것일 뿐, 나에게는 실질적인 이점이 없을 수도 있습니다. 꼭 해야 할까요?

3. 리팩토링을 하면 버그가 발생하고 불필요한 문제가 발생할 수도 있는데, 이 위험을 감수해야 합니까 ?

이는 오래된 애플리케이션 코드가 손상되는 이유 중 일부일 수도 있습니다. 하지만:

1. 리팩토링이 없으면 코드의 악취가 점점 더 지겨워지고 개발자는 쓰레기로 가득 찬 방에 들어가는 것과 같을 것입니다.

2. 리팩토링되지 않은 경우 나중에 이 코드 부분을 변경하면 다른 사람들이 이를 다시 이해해야 하므로 팀의 전체 R&D 비용이 증가하게 됩니다.

3. 재구성하지 않으면 내면에서 큰 죄책감을 느끼게 될 것입니다.

리팩토링 목적

시간비용을 조절하고 재구축 범위를 최소화하기 위해서는 요구사항을 중심으로 이번 목표를 명확히 할 필요가 있다.

목표: 이 요구 사항을 더 쉽게 완료할 수 있도록 합니다.

이를 위해서는 세 가지가 필요합니다.

1. b1 부분을 모바일 친화적인 컴포넌트로 독립적으로 만들어 모바일 비용을 절감합니다.

2. 어디를 가든 코드의 가독성을 높이고 코드를 이해하는 데 드는 비용을 줄입니다.

3. 원래 기능은 변경되지 않습니다.

리팩토링 프로세스

이전 코드를 리팩토링할 때는 각별히 주의하세요. 리팩토링 단계를 설계합니다. 각 단계는 독립적인 작은 변경이므로 각 단계를 테스트하고 각 단계의 코드를 사용하여 버그 가능성을 줄일 수 있습니다.

AR 및 HR이라고 하는 두 구성 요소 ARecord 및 HRecord를 참조하는 파트 b1의 코드 논리를 분석합니다. HR은 독립적인 구성 요소입니다. AR은 구성요소 B에 작성됩니다. b1은 하나의 컴포넌트로 패키징되어야 하므로 먼저 AR을 독립적으로 패키징해야 합니다.

1. AR 구성요소를 B에 첨부하지 않고 독립적으로 만듭니다.

먼저 B에서 AR 코드를 이동하고 약간 수정한 후 API를 캡슐화하고 유형 정의를 추가합니다. 컨텍스트를 사용하여 구성 요소 호스트를 참조하는 컨텍스트 정보를 나타냅니다. 예를 들어 포함된 페이지의 소스 정보를 이 개체에 넣을 수 있습니다. 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 구성 요소에 컨텍스트 속성을 추가하고, B 구성 요소의 원래 위치에서 새 AR 구성 요소를 참조하고, AR API에 따라 데이터를 전송하고, 페이지 로직을 테스트합니다. 테스트가 통과되었습니다.

3. AR, HR 모두 이동 조건이 있고 b1 부품 패키징을 시작합니다.

이 단계는 b1 코드와 B 구성 요소가 너무 깊게 결합되어 있기 때문에 약간 까다롭습니다. 이에 의존하는 속성과 메서드가 있습니다. 먼저 원래 사용된 속성을 제쳐두고 큰 그림부터 시작해 보겠습니다. 먼저 변경되지 않은 원래 구성 요소에 해당하는 새 구성 요소의 외부 API를 정의합니다. 원본 코드에는 유형 정의가 없기 때문에 원본 속성이 어떤 유형인지 확인하는 데 여전히 시간이 걸립니다.

유형 정의를 추가하기만 하면 됩니다.

API 디자인은 구성요소를 캡슐화할 때 매우 중요합니다. 저는 구성 요소의 API를 각각 호스트 컨텍스트와 구성 요소 데이터를 나타내는 두 개의 개체인 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를 통해 페이지에서 B로, 그런 다음 B에서 b1로 전달되므로 객체로 캡슐화하는 것이 더 편리할 것입니다. 템플릿 부분의 코드를 수정하지 않고 확장 필드의 논리만 추가하면 됩니다. 객체에 캡슐화하는 것이 적합한지 여부는 이들 10개가 다른 장소에서 사용되는지 여부에 따라 다릅니다. B의 코드에서 검색해 보면 10개의 속성 중 1개만 다른 로직에서 사용되고 나머지는 b1에서만 사용됩니다. 먼저 캡슐화할 수 있습니다.

먼저 B의 10개 API를 1개의 API로 병합하고 유형은 object이고 총 10개의 속성을 가지고 있으며 이 객체를 B에서 가져와서 b1에 전달합니다.

그런 다음 이 개체를 페이지에서 조합하여 B에 전달합니다. 동시에 B의 원래 9개 추가 API를 삭제합니다. 다른 논리에서 사용하는 1개는 유지해야 합니다. B의 경우 신규 API 1개가 추가되고 9개가 삭제되었으며, B의 API는 37개에서 29개로 줄어들고 코드도 깔끔해졌습니다.

6. 페이지 기능 테스트

Page에서 B, b1로 전달된 10개의 속성을 1개의 객체로 변경하고 테스트합니다. 테스트가 통과되었습니다.

7. b1 컴포넌트의 유형 정의에 대해 혼란스럽습니다.

두 가지 메소드가 b1의 원래 속성으로 전달되었으며 변경되지 않은 채 이동되었습니다. 코드를 보면 이 두 메소드는 현재 컨텍스트와 관련이 없으며 메소드 결과의 불리언 값만 전달하도록 변경할 수 있으며 메소드를 전달할 필요는 없습니다.

8. 페이지 기능 테스트

Page에서 B로 전달된 후 b1로 전달되는 두 가지 메소드를 불리언 값으로 변경하여 테스트해 보세요. 테스트가 통과되었습니다.

9. b1 구성 요소 유형 정의의 데이터

구성 요소에 필요한 API만 설계하고 중복 데이터를 전송하지 마십시오.

코드를 보면 마찬가지로 속성 중 하나인 데이터는 조건부 판단에만 사용되며, 그 존재만으로도 조건으로 사용되지만, 데이터의 양 자체는 적지 않다. 실제로 부울 값으로 변경될 수 있습니다.

10. 페이지 기능 테스트

Page에서 B로 전달된 데이터를 부울 값으로 변경하고 테스트합니다. 테스트가 통과되었습니다.

11. 현재 부품의 빠른 이동 조건이 충족됩니다.

이 시점에서 코드에는 이미 구성 요소를 빠르게 이동할 수 있는 조건이 있습니다. 나중에 작업하기가 더 쉬울 것입니다. 동일한 구성 요소 데이터 형식 정의를 ApproveHead에 추가하고 ApprovePage에서 ApproveFlow, ApproveHead로 데이터를 전달하고 구성 요소 참조를 ApproveFlow에서 ApproveHead로 변경합니다. 요구 사항이 충족되었습니다.

12. 페이지 기능 테스트

페이지 기능 회귀 테스트가 통과되었습니다.

요약하다

좋은 코드는 항상 눈을 즐겁게 하며, 독자는 작성자와 쉽게 합의에 도달할 수 있습니다. 모든 요구사항은 기존 코드를 개선할 수 있는 기회입니다. 이해하고 수정하기 쉬운 코드 작성을 고집하고 각 요구사항 반복에 리팩토링을 통합하여 시간이 지남에 따라 코드가 손상되는 것을 방지하세요.

그러나 수요 반복에는 주기가 있으므로 매번 완전히 중단될 수는 없습니다. 각 리팩토링의 목적을 결정하고 적당히 중지하면 비즈니스 리듬을 존중하고 최적화할 수 있습니다. 시간이 지나면 상황이 바뀔 것입니다.

"어떤 바보라도 컴퓨터가 이해할 수 있는 프로그램을 작성할 수 있습니다. 인간이 쉽게 이해할 수 있는 프로그램만이 좋은 프로그래머입니다." - 마틴 플라워

지금 클라우드 제품을 무료로 사용해 보려면 클릭하여 클라우드에서의 실제 여정을 시작하세요!

원본 링크

이 기사는 Alibaba Cloud의 원본 콘텐츠이므로 허가 없이 복제할 수 없습니다.

레이쥔(Lei Jun)은 샤오미의 더페이퍼(ThePaper) OS 전체 시스템 아키텍처를 발표하며 하위 계층이 완전히 개편됐다고 밝혔고, 유케( Yuque)는 10월 23일 장애 원인과 수리 과정을 발표했다. Java 11 및 Java 17 사용률 모두 초과 Java 8 Hugging Face의 Yuque 액세스가 제한되었습니다. 네트워크 중단이 약 10시간 동안 지속되다가 이제 정상으로 돌아왔습니다. 미국 데이터 관리국(National Data Administration)이 Oracle을 공식 공개했습니다 . Visual Studio용 Java 개발 확장 프로그램을 출시했습니다. 코드 머스크: 위키피디아 이름을 '웨이지백과사전'으로 바꾸면 10억 기부 USDMySQL 8.2.0 GA
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/yunqi/blog/10123316