自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

好的代碼重構(gòu) vs 壞的代碼重構(gòu):如何做出正確選擇?

譯文 精選
開發(fā) 項目管理
最好的重構(gòu)往往是讓終端用戶毫無察覺,卻能極大地方便開發(fā)人員。通過提升代碼的可讀性、可維護(hù)性和效率,同時保持系統(tǒng)的穩(wěn)定性,你將為整個團隊創(chuàng)造更高的工作效率并減少技術(shù)債務(wù)。

譯者 | 劉汪洋

審校 | 重樓

多年來,我招聘了許多開發(fā)人員,其中一些人堅信代碼需要頻繁重構(gòu)。然而,事實是,幾乎每次他們完成重構(gòu)并將代碼交付給其他開發(fā)人員時,大家往往發(fā)現(xiàn)這些代碼反而變得更難理解和維護(hù)。更糟糕的是,重構(gòu)后的代碼通常運行效率更低,且問題頻發(fā)。

需要明確的是,重構(gòu)本身并無不妥。它是保持代碼庫健康和可持續(xù)發(fā)展的關(guān)鍵。然而,不當(dāng)?shù)闹貥?gòu)會帶來負(fù)面影響,試圖改進(jìn)代碼時出現(xiàn)的錯誤,往往會適得其反,這種情況并不罕見。

接下來,我們將探討如何區(qū)分好的重構(gòu)與不良重構(gòu),并討論如何避免成為那個讓團隊成員都不愿意接觸代碼庫的開發(fā)者。

重構(gòu)的優(yōu)點、缺點與陷阱

在編程中,抽象既可能帶來好處,也可能造成問題,關(guān)鍵在于何時以及如何應(yīng)用。下面,我們將探討一些常見的陷阱,并討論如何避免這些問題。

1. 大幅改變編碼風(fēng)格

我經(jīng)??吹介_發(fā)人員在重構(gòu)過程中完全改變編碼風(fēng)格,這是最常見的錯誤之一。通常,這種情況發(fā)生在開發(fā)人員來自不同背景或?qū)δ撤N編程范式有強烈偏好的情況下。

讓我們來看一個例子。假設(shè)我們有一段需要重構(gòu)的代碼:

重構(gòu)前

// ?? 這段代碼可以更簡潔
function processUsers(users: User[]) {
  const result = [];
  for (let i = 0; i < users.length; i++) {
    if (users[i].age >= 18) {
      const formattedUser = {
        name: users[i].name.toUpperCase(),
        age: users[i].age,
        isAdult: true
      };
      result.push(formattedUser);
    }
  }
  return result;
}

不好的重構(gòu)

import * as R from 'ramda';

// ?? 采用了完全不同的風(fēng)格和庫
const processUsers = R.pipe(
  R.filter(R.propSatisfies(R.gte(R.__, 18), 'age')),
  R.map(R.applySpec({
    name: R.pipe(R.prop('name'), R.toUpper),
    age: R.prop('age'),
    isAdult: R.always(true)
  }))
);

盡管這個重構(gòu)版本可能會受到函數(shù)式編程愛好者的青睞,但它引入了一個新庫(Ramda)以及一種完全不同的編碼風(fēng)格。對于不熟悉這種方法的團隊來說,維護(hù)起來可能會非常困難。

好的重構(gòu)

// ? 更加簡潔且更符合慣例
function processUsers(users: User[]): FormattedUser[] {
  return users
    .filter(user => user.age >= 18)
    .map(user => ({
      name: user.name.toUpperCase(),
      age: user.age,
      isAdult: true
    }));
}

這個版本通過使用 JavaScript 的 filtermap 方法改進(jìn)了原始代碼。它不僅更簡潔易讀,而且保持了代碼庫的連貫性,沒有引入新的編程范式或外部依賴。

2. 不必要的抽象

我曾經(jīng)遇到一位開發(fā)人員,他在不了解底層代碼的情況下,添加了大量不必要的抽象。他將一些不應(yīng)歸類在一起的組件強行合并,導(dǎo)致代碼變得難以維護(hù)。此外,他還將不應(yīng)合并的配置(例如不同 API 需要的配置)混在一起,使得后續(xù)管理變得復(fù)雜。

重構(gòu)前

// ?? 這段代碼可以更簡潔
function processUsers(users: User[]) {
  const result = [];
  for (let i = 0; i < users.length; i++) {
    if (users[i].age >= 18) {
      const formattedUser = {
        name: users[i].name.toUpperCase(),
        age: users[i].age,
        isAdult: true
      };
      result.push(formattedUser);
    }
  }
  return result;
}

不好的重構(gòu)

// ?? 這里引入了過多的層級和抽象
class UserProcessor {
  private users: User[];

  constructor(users: User[]) {
    this.users = users;
  }

  public process(): FormattedUser[] {
    return this.filterAdults().formatUsers();
  }

  private filterAdults(): UserProcessor {
    this.users = this.users.filter(user => user.age >= 18);
    return this;
  }

  private formatUsers(): FormattedUser[] {
    return this.users.map(user => ({
      name: this.formatName(user.name),
      age: user.age,
      isAdult: true
    }));
  }

  private formatName(name: string): string {
    return name.toUpperCase();
  }
}

const processUsers = (users: User[]): FormattedUser[] => {
  return new UserProcessor(users).process();
};

這個重構(gòu)引入了一個多方法的類,雖然看似更“面向?qū)ο蟆?,但實際上增加了不必要的復(fù)雜性,導(dǎo)致代碼變得難以理解和維護(hù)。

好的重構(gòu)

// ? 更加簡潔且更符合慣例
const isAdult = (user: User): boolean => user.age >= 18;

const formatUser = (user: User): FormattedUser => ({
  name: user.name.toUpperCase(),
  age: user.age,
  isAdult: true
});

function processUsers(users: User[]): FormattedUser[] {
  return users.filter(isAdult).map(formatUser);
}

這個版本通過將邏輯分解為小而可重用的函數(shù),避免了不必要的抽象,使代碼更加簡潔明了且易于維護(hù)。

3. 增加不一致性

有些開發(fā)人員在試圖優(yōu)化代碼時,會對代碼庫的某個部分進(jìn)行改動,使其與其他部分的實現(xiàn)方式完全不同。這種不一致會導(dǎo)致其他開發(fā)人員在處理不同風(fēng)格的代碼時感到困惑和沮喪。

假設(shè)我們有一個 React 應(yīng)用,其中所有數(shù)據(jù)獲取都使用 React Query:

// 應(yīng)用程序的其他部分
import { useQuery } from 'react-query';

function UserProfile({ userId }) {
  const { data: user, isLoading } = useQuery(['user', userId], fetchUser);

  if (isLoading) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

然而,某位開發(fā)人員在一個組件中決定改用 Redux Toolkit:

// 不一致的做法
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPosts } from './postsSlice';

function PostList() {
  const dispatch = useDispatch();
  const { posts, status } = useSelector((state) => state.posts);

  useEffect(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  if (status === 'loading') return <div>Loading...</div>;
  return <div>{posts.map(post => <div key={post.id}>{post.title}</div>)}</div>;
}

這種不一致做法令人困惑,因為它僅為一個組件引入了完全不同的狀態(tài)管理方式。

更好的做法是保持一致,繼續(xù)使用 React Query:

// 保持一致的做法
import { useQuery } from 'react-query';

function PostList() {
  const { data: posts, isLoading } = useQuery('posts', fetchPosts);

  if (isLoading) return <div>Loading...</div>;
  return <div>{posts.map(post => <div key={post.id}>{post.title}</div>)}</div>;
}

這個版本在整個應(yīng)用中保持了一致性,繼續(xù)使用 React Query 進(jìn)行數(shù)據(jù)獲取。這樣不僅簡化了代碼,還避免了其他開發(fā)人員因單一組件而不得不學(xué)習(xí)新的模式。

請記住,代碼庫中的一致性至關(guān)重要。如果你需要引入新的模式,請確保先與團隊達(dá)成共識,而不是孤立地引入不一致性。

4. 在未理解代碼的情況下進(jìn)行重構(gòu)

我見過的最大問題之一是,有些開發(fā)人員在尚未充分理解代碼的情況下進(jìn)行重構(gòu),以此作為學(xué)習(xí)代碼的手段。這種做法往往會帶來嚴(yán)重后果。經(jīng)驗表明,你應(yīng)該至少在使用某段代碼 6-9 個月后再進(jìn)行重構(gòu),否則可能會引入 bug 或降低性能。

重構(gòu)前

// ?? 這里的硬編碼較多
function fetchUserData(userId: string) {
  const cachedData = localStorage.getItem(`user_${userId}`);
  if (cachedData) {
    return JSON.parse(cachedData);
  }

  return api.fetchUser(userId).then(userData => {
    localStorage.setItem(`user_${userId}`, JSON.stringify(userData));
    return userData;
  });
}

不好的重構(gòu)

// ?? 緩存機制被移除
function fetchUserData(userId: string) {
  return api.fetchUser(userId);
}

重構(gòu)者可能認(rèn)為他們簡化了代碼,但實際上他們移除了減少 API 調(diào)用并提升性能的緩存機制。

好的重構(gòu)

// ? 保留現(xiàn)有行為的同時簡化了代碼
async function fetchUserData(userId: string) {
  const cachedData = await cacheManager.get(`user_${userId}`);
  if (cachedData) {
    return cachedData;
  }

  const userData = await api.fetchUser(userId);
  await cacheManager.set(`user_${userId}`, userData, { expiresIn: '1h' });
  return userData;
}

這個重構(gòu)保留了緩存行為,并通過使用更復(fù)雜的緩存管理器(帶有過期時間設(shè)置)來優(yōu)化緩存機制。

5. 理解業(yè)務(wù)背景

在未充分理解業(yè)務(wù)背景的情況下進(jìn)行重構(gòu),可能導(dǎo)致項目失敗。我曾在一家維護(hù)大量遺留代碼的公司工作,并領(lǐng)導(dǎo)了一個將其電商平臺遷移到新技術(shù)上的項目。當(dāng)時我們選擇了 Angular.js 作為技術(shù)棧。然而,這家公司高度依賴 SEO,而我們卻構(gòu)建了一個緩慢且臃腫的單頁應(yīng)用程序(SPA)。兩年后,發(fā)布的結(jié)果是一個更慢且漏洞百出的難以維護(hù)的網(wǎng)站。原因在于,作為項目負(fù)責(zé)人,我之前從未在這個網(wǎng)站上工作過,缺乏對其業(yè)務(wù)背景的理解。我當(dāng)時年輕且經(jīng)驗不足。

讓我們來看看一個類似的現(xiàn)代例子:

不好的重構(gòu)

// ?? 為依賴 SEO 的網(wǎng)站構(gòu)建單頁應(yīng)用程序是個糟糕的主意
function App() {
  return (
    <Router>
    <Switch>
    <Route path="/product/:id" component={ProductDetails} />
    </Switch>

    </Router>

  );
}

這種代碼看似現(xiàn)代且簡潔,但它完全依賴客戶端渲染。對于一個高度依賴 SEO 的電商網(wǎng)站,這可能會帶來災(zāi)難性的后果。

好的重構(gòu)

// ? 為 SEO 優(yōu)化的網(wǎng)站使用服務(wù)器端渲染
export const getStaticProps: GetStaticProps = async () => {
  const products = await getProducts();
  return { props: { products } };
};

export default function ProductList({ products }) {
  return (
    <div>
    ...
    </div>

  );
}

這個基于 Next.js 的方法提供了開箱即用的服務(wù)器端渲染功能,對 SEO 至關(guān)重要。它不僅提升了初始頁面的加載速度,還為網(wǎng)絡(luò)連接較慢的用戶提供了更好的體驗。Remix 也適用于這種場景,提供了類似的服務(wù)器端渲染和 SEO 優(yōu)化優(yōu)勢。

6. 過度整合代碼

我曾招聘過一名程序員,他在加入團隊的第一天就開始重構(gòu)代碼。我們有許多 Firebase 函數(shù),不同函數(shù)在超時和內(nèi)存分配方面的配置有所不同。 這是我們原有的設(shè)置:

重構(gòu)前

// ?? 代碼庫中有超過 40 處相同的代碼,或許可以進(jìn)行整合
export const quickFunction = functions
  .runWith({ timeoutSeconds: 60, memory: '256MB' })
  .https.onRequest(...);

export const longRunningFunction = functions
  .runWith({ timeoutSeconds: 540, memory: '1GB' })
  .https.onRequest(...);

這位新人決定將所有這些函數(shù)封裝到一個 createApi 函數(shù)中。

不好的重構(gòu)

// ?? 盲目地整合了不應(yīng)整合的設(shè)置
const createApi = (handler: RequestHandler) => {
  return functions
    .runWith({ timeoutSeconds: 300, memory: '512MB' })
    .https.onRequest((req, res) => handler(req, res));
};

export const quickFunction = createApi(handleQuickRequest);
export const longRunningFunction = createApi(handleLongRunningRequest);

這種重構(gòu)將所有 API 的配置統(tǒng)一為相同的參數(shù),并且沒有提供單獨覆蓋的選項。問題在于,不同的函數(shù)可能需要不同的配置參數(shù)。

更好的方法是允許每個 API 傳遞自定義的 Firebase 配置:

好的重構(gòu)

// ? 設(shè)置了良好的默認(rèn)值,但允許覆蓋
const createApi = (handler: RequestHandler, options: ApiOptions = {}) => {
  return functions
    .runWith({ timeoutSeconds: 300, memory: '512MB', ...options })
    .https.onRequest((req, res) => handler(req, res));
};

export const quickFunction = createApi(handleQuickRequest, { timeoutSeconds: 60, memory: '256MB' });
export const longRunningFunction = createApi(handleLongRunningRequest, { timeoutSeconds: 540, memory: '1GB' });

這種方法在保留抽象優(yōu)勢的同時,也保留了必要的靈活性。在整合或抽象代碼時,一定要考慮所屬服務(wù)的具體用例。不要為了追求“簡潔”而犧牲代碼的靈活性。確保你的抽象能夠滿足原始實現(xiàn)的所有需求。

最重要的是,在開始“改進(jìn)”代碼之前,必須深入理解它。我們曾在下一次部署 API 時遇到問題,而這些問題本可以通過避免這種盲目重構(gòu)來規(guī)避。

如何正確地進(jìn)行重構(gòu)

正確地進(jìn)行代碼重構(gòu)至關(guān)重要。盡管代碼庫不可能完美無缺,并且重構(gòu)在某些時候是必要的,但重構(gòu)時必須保持代碼庫的一致性,并在深入理解代碼的基礎(chǔ)上謹(jǐn)慎處理抽象。

以下是一些成功重構(gòu)的建議:

  1. 逐步進(jìn)行:采取小幅、可控的更改,而非大規(guī)模的重寫。
  2. 深入理解代碼:在進(jìn)行重大重構(gòu)或引入新抽象之前,務(wù)必充分理解現(xiàn)有代碼。
  3. 保持與現(xiàn)有代碼風(fēng)格一致:一致性是提升代碼可維護(hù)性的關(guān)鍵。
  4. 避免過度抽象:保持簡單,除非確實有必要增加復(fù)雜性。
  5. 避免引入新庫:特別是那些風(fēng)格迥異的庫,除非團隊已達(dá)成共識,否則不要輕易引入。
  6. 在重構(gòu)前編寫測試,并在重構(gòu)過程中更新測試。這能確保在改進(jìn)過程中維持原有功能的穩(wěn)定性。
  7. 確保團隊成員遵循這些原則:確保所有團隊成員都遵循這些重構(gòu)原則,每個成員都應(yīng)對其負(fù)責(zé)。

提高重構(gòu)質(zhì)量的工具和技巧

為確保重構(gòu)能有效提升代碼質(zhì)量,可以考慮以下工具和技巧:

Linting 工具

使用 Linting 工具強制執(zhí)行一致的代碼風(fēng)格并捕捉潛在問題。Prettier 可以自動格式化代碼以保持風(fēng)格一致,而 Eslint 能進(jìn)行更細(xì)致的檢查,且支持通過自定義插件適應(yīng)團隊的特殊需求。

代碼審查

在合并重構(gòu)后的代碼之前,進(jìn)行徹底的代碼審查并獲取同事的反饋。這樣有助于及早發(fā)現(xiàn)潛在問題,確保代碼符合團隊標(biāo)準(zhǔn)和預(yù)期。

測試

編寫并運行測試,確保重構(gòu)不會破壞現(xiàn)有功能。Vitest 是一個快速、穩(wěn)定且易于使用的測試工具,默認(rèn)情況下無需復(fù)雜配置。對于視覺測試,可以使用 StorybookReact Testing Library 是測試 React 組件的優(yōu)秀工具,而 Angular Testing Library 及其他變種適用于不同框架。

AI 工具

合理利用 AI 工具進(jìn)行重構(gòu),特別是那些能夠匹配現(xiàn)有編碼風(fēng)格和約定的工具。 Visual Copilot 是一個前端開發(fā)中非常有用的 AI 工具,它有助于將設(shè)計轉(zhuǎn)化為代碼,同時保持編碼風(fēng)格的一致性,并正確使用設(shè)計系統(tǒng)的組件和標(biāo)記。這些工具和技巧可以幫助你在重構(gòu)過程中保持代碼質(zhì)量,確保重構(gòu)帶來的改進(jìn)是可持續(xù)且有意義的。

這些工具和技巧可以幫助你在重構(gòu)過程中保持代碼質(zhì)量,確保重構(gòu)帶來的改進(jìn)是可持續(xù)且有意義的。

結(jié)論

重構(gòu)是軟件開發(fā)中不可或缺的一部分,但它必須經(jīng)過深思熟慮,并尊重現(xiàn)有代碼庫和團隊的工作方式。重構(gòu)的目標(biāo)是在不改變代碼外部行為的情況下,優(yōu)化其內(nèi)部結(jié)構(gòu)。

請記住,最好的重構(gòu)往往是讓終端用戶毫無察覺,卻能極大地方便開發(fā)人員。通過提升代碼的可讀性、可維護(hù)性和效率,同時保持系統(tǒng)的穩(wěn)定性,你將為整個團隊創(chuàng)造更高的工作效率并減少技術(shù)債務(wù)。

所以,下次當(dāng)你有“大計劃”要改進(jìn)某段代碼時,先停下來,深入理解這段代碼,評估改動可能帶來的影響,并選擇團隊會感謝的漸進(jìn)式改進(jìn)方法。未來的你(以及你的同事們)一定會感激你這種周到且維護(hù)良好的代碼庫的做法。

譯者介紹

劉汪洋,51CTO社區(qū)編輯,昵稱:明明如月,一個擁有 5 年開發(fā)經(jīng)驗的某大廠高級 Java 工程師,擁有多個主流技術(shù)博客平臺博客專家稱號,博客閱讀量 400W+,粉絲 3W+。2022 年騰訊云優(yōu)秀創(chuàng)作者,2022 年阿里云技術(shù)社區(qū)最受歡迎技術(shù)電子書 TOP 10 《性能優(yōu)化方法論》作者,慕課網(wǎng):剖析《阿里巴巴 Java 開發(fā)手冊》、深度解讀《Effective Java》 技術(shù)專欄作者。

原文標(biāo)題:Good Refactoring vs Bad Refactoring,作者:Steve Sewell

鏈接:https://dev.to/builderio/good-refactoring-vs-bad-refactoring-2361

責(zé)任編輯:火鳳凰 來源: 51CTO
相關(guān)推薦

2024-12-11 18:24:29

2018-08-24 21:25:02

編程語言代碼重構(gòu)GitHub

2024-09-27 12:04:48

2012-12-17 12:58:18

WebjQuery重構(gòu)

2021-08-03 08:13:48

重構(gòu)API代碼

2022-05-07 10:01:20

好代碼壞代碼

2024-02-22 10:27:00

Python開發(fā)

2020-05-19 08:06:57

代碼重構(gòu)代碼開發(fā)

2023-08-18 14:10:00

CDN數(shù)據(jù)中心

2022-07-04 07:37:51

模板模式重構(gòu)

2012-07-27 10:30:12

重構(gòu)

2011-08-16 09:47:58

編程

2019-02-18 16:21:47

華為代碼重構(gòu)

2022-12-26 00:02:24

重構(gòu)代碼軟件

2019-04-03 08:10:17

代碼架構(gòu)信息

2018-04-25 10:03:28

前端重構(gòu)Javascript

2011-09-05 10:30:51

重構(gòu)代碼庫業(yè)務(wù)模型

2024-08-06 12:35:42

C#代碼重構(gòu)

2022-08-02 08:07:24

單元測試代碼重構(gòu)

2013-10-21 17:54:00

代碼重構(gòu)修改
點贊
收藏

51CTO技術(shù)棧公眾號