从零开始学重构——重构的流程及基础重构手法

重构的流程

重构手法

  正如上一次所讲的那样,重构有两个基本条件,一是要保持代码在重构前后的行为基本不变,二是整个过程是受控且尽可能少地产生错误。尤其是对于第二点,产生了一系列的重构手法,每种重构手法都是一系列简单而机械的操作步骤,通过遵循这一系列的操作来实现代码的结构性调整。因此,重构的整个过程就是不断运用不同重构手法的过程,是一个相对有章可循的流程。
  重构手法有大有小,大的重构手法一般由若干小的基础重构组成,进而聚沙成塔实现对代码结构大幅度的调整。完整的重构列表请参见《重构,改善既有代码的设计》一书。
  例如,replace conditional with polymorphism这项复杂重构手法,就至少需要使用self encapsulate, extract method, move method, pull down method这四种基础重构手法。因此在学习类级别的复杂重构手法前,需要先掌握行级别和方法级别的基础重构手法。
  
  重构手法的分解

重构步骤

  重构的宏观步骤一般有如下两种:自上而下式和自下而上式。
  自上而下的重构在重构前,心中已经大致知道重构后的代码将会是什么形态,然后至上而下地将步骤分解出来,并使用相应的重构步骤一一实现,最终达到重构后的形态。其流程为:
  
1. 识别代码中的坏味道
2. 运用设计原则,构思出修改后的目标状态
3. 将目标状态分解为一或多项重构步骤
4. 运用重构步骤

  自下而上的重构则对重构后的代码没有一个完整而清晰的认识。一般而言,每种重构手法都有助于我们解决某种类型的代码坏味,而自下而上的重构则针对每个发现的代码坏味直接运用对应的重构手法,直到没有明显的坏味,此时的代码即能自动满足某种设计模式。是一种迭代的思路,也是所谓重构到模式的思路。其流程为:
  
1. 识别代码中的坏味道
2. 运用一项或多项重构步骤,消除坏味
3. 重复1-2,直到没有明显坏味

  在一般的情况下,这两种重构流程并不是互斥的,经常交错进行或互相包含。如先运用自上而下的方法识别出代码中的坏味,然后根据设计原则重构到某个实现,再运用自下而上的方法重新寻找新的坏味,迭代重构。

基础重构手法

  由于基础重构手法比较多,而且相对比较简单。因此先列出常用的基础重构手法和简单介绍,并在最后的实践案例中结合基础重构手法来重构代码。

rename(重命名变量/方法/类)

  • 坏味:含义不清的命名
  • 说明:变量名应当体现出变量的作用和含义、方法名应当表现出方法的效果、类名也应提示类的职责和在继承体系中的位置。
  • 操作方法:IntelliJ Shift+F6

reorder(调整语句顺序)

  • 坏味:变量的申请和使用分离太远
  • 说明:变量的使用应当尽可能离使用近一些,否则会扩大变量的作用域,在重构时也会产生困难。
  • 操作方法:IntelliJ Alt+Shift+↑↓ 针对无副作用的语句,直接调整语句位置。

split for/block(拆分for循环/代码块)

  • 坏味:一个循环或代码块中同时操作了多个变量或执行了多个职责
  • 说明:一个循环中若有太多变量要计算,不利于将此循环提取为单独方法。
  • 操作方法:

    1. 将循环复制一次
    2. 每个循环中只保留一个变量的计算
    3. 将循环提取为独立方法
    4. 将所有循环的出现替换为方法的调用

guard clauses(卫语句)

  • 坏味:过深的条件嵌套
  • 说明:先判断跳出/过滤的条件,并直接return或continue,可除去多余的else嵌套深度。
  • 操作方法:

    1. IntelliJ 在if语句上Alt+Enter,选择invert if,可倒转if和else语句
    2. IntelliJ 在else语句上Alt+Enter,选择remove redundant else

extract variable(提取变量)

  • 坏味:单条语句过长,含义不清
  • 说明:将部分语句提取出变量,并为变量起一个能够解释变量含义的名称来替代注释
  • 操作说明:IntelliJ Ctrl+Alt+V

extract method(提取方法)

  • 坏味:单个方法过长,含义不清
  • 说明:将做同一件事的代码提取出方法(一般为计算某个变量,或进行单个复杂操作),并为方法起一个能够解释”这件事”的名称来替代注释
  • 操作说明:IntelliJ Ctrl+Alt+M,需要考虑返回和参数的列表,返回不能超过1个变量

inline method(内联方法)

  • 坏味:方法只有一行代码,且内容本身已经很明确(多行也可以,但若原方法有返回值,则会比较复杂,不推荐)
  • 说明:方法的作用是聚合操作并提供注释信息,若方法内容已经明确,则方法本身就起不到作用,反而增加复杂度
  • 操作说明:将方法内容复制后,替换方法调用的部分。再删除方法本身

add parameter(方法增加参数)

  • 坏味:方法主体只有部分变量不同
  • 说明:可以提取变化的部分成为参数,从而合并两个相似的方法
  • 操作说明:

    1. 将变量的部分提取为变量,并提到方法的最开始处
    2. 将方法剩余的内容提取为一个新的方法,新方法会含有新的参数
    3. 将原来的老方法内联

案例实践

重构前

private Set<String> channelColumns;

public String generateSql() {
    String channelColumnClauseTemp = StringUtil.flat(channelColumns, ",", "", "");
    String channelColumnClause;
    Set<String> columns = new TreeSet<>();
    for (String str : channelColumns) {
        if (ChannelId.CLT_BUS_EML_ADR.toString().equals(str)) {
            columns.add(ChannelId.CLT_EML_ADR.toString());
        } else {
            columns.add(str);
        }
    }
    channelColumnClause = StringUtil.flat(columns, ",", "", "");

    String channelColumnsReviewTemp = "";
    String channelColumnsReview = "";
    if (!channelColumns.isEmpty()) {
        channelColumnsReviewTemp = channelColumnClauseTemp +
                (channelColumns.contains(idTypeColumn) ? "" : ("," + idTypeColumn)) + ",batch_id";
        channelColumnsReview = channelColumnClause +
                (channelColumns.contains(idTypeColumn) ? "" : ("," + idTypeColumn)) + ",batch_id";
    } else {
        channelColumnsReviewTemp = idTypeColumn + ",batch_id";
        channelColumnsReview = idTypeColumn + ",batch_id";
    }

    StringBuffer vsql = new StringBuffer();
    vsql.append("insert into ").append(Constant.DB_SCHEMA).append(".").append(tableName)
            .append(" (").append(channelColumnsReview).append(")")
            .append(" select distinct ").append(channelColumnsReviewTemp.replace(Constant.CITYNAME_COLUMN, "isnull(" + Constant.CITYNAME_COLUMN + &#
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们开始重构axios! 首先,我们需要安装TypeScript和相关的依赖。运行以下命令: ``` npm install typescript @types/node @types/qs @types/form-data ``` 接下来,我们需要创建一个tsconfig.json文件。在项目根目录下创建一个名为tsconfig.json的文件,并将以下内容复制到其中: ```json { "compilerOptions": { "lib": ["es6", "dom"], "target": "es5", "module": "commonjs", "moduleResolution": "node", "declaration": true, "sourceMap": true, "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "exclude": ["node_modules"] } ``` 现在,我们可以开始重构axios的代码了。 首先,我们需要将axios的代码转换为TypeScript。我们可以将所有的JavaScript文件重命名为.ts文件,并使用TypeScript的语法更新代码。例如,我们可以使用类型注释来定义函数的参数类型和返回类型。 下面是一个使用TypeScript语法重构的示例: ```typescript import * as http from 'http'; import * as https from 'https'; import * as url from 'url'; import * as zlib from 'zlib'; import { Cancel, CancelToken } from './cancel'; import { isFormData } from './utils'; import settle from './core/settle'; import buildURL from './helpers/buildURL'; import parseHeaders from './helpers/parseHeaders'; import createError from './core/createError'; import enhanceError from './core/enhanceError'; import defaults from './defaults'; interface AxiosRequestConfig { url?: string; method?: string; baseURL?: string; headers?: any; params?: any; data?: any; timeout?: number; withCredentials?: boolean; responseType?: XMLHttpRequestResponseType; xsrfCookieName?: string; xsrfHeaderName?: string; onDownloadProgress?: (progressEvent: any) => void; onUploadProgress?: (progressEvent: any) => void; cancelToken?: CancelToken; } interface AxiosResponse<T = any> { data: T; status: number; statusText: string; headers: any; config: AxiosRequestConfig; request?: any; } interface AxiosError<T = any> extends Error { config: AxiosRequestConfig; code?: string; request?: any; response?: AxiosResponse<T>; isAxiosError: boolean; } interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> {} interface Axios { defaults: AxiosRequestConfig; interceptors: { request: AxiosInterceptorManager<AxiosRequestConfig>; response: AxiosInterceptorManager<AxiosResponse>; }; request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>; get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; delete<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; head<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; options<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; } interface AxiosInstance extends Axios { <T = any>(config: AxiosRequestConfig): AxiosPromise<T>; <T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; } interface AxiosStatic extends AxiosInstance { create(config?: AxiosRequestConfig): AxiosInstance; CancelToken: CancelTokenStatic; Cancel: CancelStatic; isCancel: (value: any) => boolean; } interface AxiosInterceptorManager<T> { use(resolved: ResolvedFn<T>, rejected?: RejectedFn): number; eject(id: number): void; } interface ResolvedFn<T> { (val: T): T | Promise<T>; } interface RejectedFn { (error: any): any; } interface CancelToken { promise: Promise<Cancel>; reason?: Cancel; throwIfRequested(): void; } interface Canceler { (message?: string): void; } interface CancelExecutor { (cancel: Canceler): void; } interface CancelTokenSource { token: CancelToken; cancel: Canceler; } interface CancelTokenStatic { new (executor: CancelExecutor): CancelToken; source(): CancelTokenSource; } interface Cancel { message?: string; } interface CancelStatic { new (message?: string): Cancel; } function axios<T = any>(config: AxiosRequestConfig): AxiosPromise<T> { return dispatchRequest(config); } function createInstance(config: AxiosRequestConfig): AxiosInstance { const context = new Axios(config); const instance = Axios.prototype.request.bind(context); Object.assign(instance, Axios.prototype, context); return instance as AxiosInstance; } const axiosInstance = createInstance(defaults); axiosInstance.create = function create(config) { return createInstance(Object.assign(defaults, config)); }; function getDefaultAdapter() { let adapter; if (typeof XMLHttpRequest !== 'undefined') { adapter = require('./adapters/xhr'); } else if (typeof http !== 'undefined') { adapter = require('./adapters/http'); } else if (typeof https !== 'undefined') { adapter = require('./adapters/http'); } return adapter; } function dispatchRequest<T = any>(config: AxiosRequestConfig): AxiosPromise<T> { throwIfCancellationRequested(config.cancelToken); processConfig(config); return getDefaultAdapter()(config).then((response) => { return transformResponseData(response); }, (error) => { if (error && error.response) { error.response = transformResponseData(error.response); } return Promise.reject(error); }).then((response) => { settle(resolve, reject, response); return response; }, (error) => { settle(resolve, reject, enhanceError(error)); return Promise.reject(enhanceError(error)); }); } function processConfig(config: AxiosRequestConfig): void { config.url = transformURL(config); config.headers = transformHeaders(config); config.data = transformData(config); config.params = transformParams(config); } function transformURL(config: AxiosRequestConfig): string { const { url, params, baseURL } = config; return buildURL(url!, params, baseURL); } function transformHeaders(config: AxiosRequestConfig): any { const { headers = {}, data } = config; return Object.assign(headers.common || {}, headers[config.method!] || {}, headers, data ? data.headers : null); } function transformData(config: AxiosRequestConfig): any { const { data } = config; return isFormData(data) ? data : JSON.stringify(data); } function transformParams(config: AxiosRequestConfig): any { const { params } = config; return params ? params : null; } function transformResponseData(response: AxiosResponse): AxiosResponse { response.data = transformData(response); response.headers = parseHeaders(response.headers, response.config); response.data = transformData(response); return response; } function throwIfCancellationRequested(cancelToken?: CancelToken): void { if (cancelToken) { cancelToken.throwIfRequested(); } } export default axiosInstance; ``` 现在我们已经将axios的代码转换为了TypeScript,接下来我们需要更新一下项目的结构。 我们可以将所有的TypeScript代码放在一个src目录下,并将编译后的JavaScript代码放在一个dist目录下。这样做可以使我们的代码更加结构化和易于管理。 接下来,我们需要更新package.json文件中的scripts字段,以便使用TypeScript编译我们的代码。在scripts字段中添加以下内容: ```json "scripts": { "build": "tsc" } ``` 现在我们可以运行npm run build命令来编译我们的代码了。 最后,我们需要更新我们的引用代码,以便使用重构后的axios。例如,我们可以使用以下代码来发送一个GET请求: ```typescript import axios from './dist/axios'; axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); ``` 这就是使用TypeScript从零重构axios的过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值