阅读《重构改善既有代码的设计》实践---重构+新增功能

契机

上个博客写了何时重构,在公司项目新增功能的时候,遇到了一个接口api,完美符合以上几点。

export function getDirections(data) {
  let coordinates = '';
  let coordsArray = [];
  let paramObject = null;
  let dataParams = null; // ?geometries=geojson&access_token=&alternatives=true&steps=true"
  let startPoint = '',
    directionsInfo = store.state.directions;
  if (directionsInfo.routeStartPoint.geometry) {
    startPoint = directionsInfo.routeStartPoint.geometry.coordinates
  }
  coordsArray.push(startPoint);

  let pastsPointsInfo = []; //途经点
  if (store.state.waypoint.waypointsPast.length > 0) {
    for (let waypoint of store.state.waypoint.waypointsPast) {
      if(waypoint == undefined) continue;
      if (!data.first) {
        pastsPointsInfo.push({
          id: waypoint.id,
          text: waypoint.text,
          coordinates: waypoint.geometry.coordinates
        });
      }
      coordsArray.push(waypoint.geometry.coordinates)
    }
  }
  let endPoint = ''; //终点
  if (directionsInfo.routeEndPoint.geometry) {
    endPoint = directionsInfo.routeEndPoint.geometry.coordinates;
  }
  coordsArray.push(endPoint);

  coordinates = exChangeCode(coordsArray, 'encode');
  paramObject = {
    // geometries: 'geojson',
    access_token: '',
    alternatives: true,
    steps: true,
    location: coordinates,
    coordtype: 'sg_polyline'
  }
  if (!data.first) {
    router.push({
      query: {
        fromname: JSON.stringify({
          id: directionsInfo.routeStartPoint.id,
          text: directionsInfo.routeStartPoint.text,
          coordinates: exChangeCode(directionsInfo.routeStartPoint.geometry.coordinates, 'encode')
        }),
        waypointsPast: JSON.stringify(pastsPointsInfo),
        toname: JSON.stringify({
          id: directionsInfo.routeEndPoint.id,
          text: directionsInfo.routeEndPoint.text,
          coordinates: exChangeCode(directionsInfo.routeEndPoint.geometry.coordinates, 'encode')
        }),
        queryType: 'direction',
        exclude: data.exclude
      }
    });
  }
  // //路线偏好设置,(系统推荐、最短路程,不给exclude参数),高速优先(exclude:toll),不做高速(exclude:motorway)
  if (data && data.exclude) {
    Object.assign(paramObject, {
      exclude: data.exclude
    }) //
  }
  dataParams = getParamString(paramObject); //将对象转换成字符串格式
  return new Promise((resolve, reject) => {
    fetch({
      url: `脱敏/driving/${coordinates}${dataParams}`,
      method: 'get'
    }).then(response => {
      let resData = response;
      resData.queryParameters = store.state.waypoint.waypointsPast;
      resolve(resData);

    }).catch(error => {
      reject(error);
    });
  });
}

密密麻麻一长串的函数,真正要阅读起来,还真实有点费劲。

新功能背景

这是一个驾车路径规划的接口,新增了公交和步行路径规划。
因为触发查询的条件几乎一致,最小化的改动代码,从vuex里面的驾车路径规划action入手。

原功能,仅有路径导航。

  directions({
    commit
  }, queryParams) {
    // 这里不重点展开,只针对上述文件进行重构
  },

在路径规划action添加多个入口,保证旧功能不变的前提,新增功能

  directions({ commit }, queryParams) {
    const routeType = store.state.type;
    if (!routeType || routeType === "drive") {
      directionsDriver({ commit }, queryParams);
    } else if (routeType === "bus") {
      directionsBus({ commit }, queryParams);
    } else if (routeType === "walk") {
      directionsWalk({ commit }, queryParams);
    }
  },

重构开始

重复代码提取

因为添加了相似的功能,所以大概率会有很多需要复用的代码。
但是大多数清空和赶工的时候,直接重新拷贝一份代码再做修改,导致出现了太多重复代码。
1 getCoordinates
2 setUrlParams

function getCoordinates(directionsInfo, needPassPoint, waypointsPast) {
  // 重构方法:提炼函数(106) 将过长函数中,相同操作的函数提取到一个小函数内
  let coordsArray = [];
  let startPoint = "";
  if (directionsInfo.routeStartPoint.geometry) {
    startPoint = directionsInfo.routeStartPoint.geometry.coordinates;
  }
  coordsArray.push(startPoint);
  if (needPassPoint) {
    if (waypointsPast.length > 0) {
      for (let waypoint of waypointsPast) {
        // 重构方法:拆分循环(227)
        // 将循环中无法拆分到两部分内容,拆分成2个独立到任务setUrlParams方法中重新循环处理
        if (waypoint == undefined) continue;
        coordsArray.push(waypoint.geometry.coordinates);
      }
    }
  }
  let endPoint = ""; //终点
  if (directionsInfo.routeEndPoint.geometry) {
    endPoint = directionsInfo.routeEndPoint.geometry.coordinates;
  }
  coordsArray.push(endPoint);

  return exChangeCode(coordsArray, "encode");
}
function setUrlParams(obj) {
  // 重构方法:保持对象完整(319)引入state,data,避免过长变量列表
  // 重构方法:引入参数对象(140)使用一个对象参数,函数解析获取参数列表,便于扩展
  const { type, state, data } = obj;
  const { exclude, first } = data || {};
  const directionsInfo = state.directions;
  const waypointsPast = state.waypoint.waypointsPast;
  const pastsPointsInfo = []; // ?作用是什么 途经点
  if (waypointsPast.length > 0) {
    for (let waypoint of waypointsPast) {
      // 重构方法:拆分循环(227)
      // 将循环中无法拆分到两部分内容,拆分成2个独立到任务setUrlParams方法中重新循环处理
      if (waypoint == undefined) continue;
      if (!first) {
        pastsPointsInfo.push({
          id: waypoint.id,
          text: waypoint.text,
          coordinates: waypoint.geometry.coordinates
        });
      }
    }
  }
  router.push({
    query: {
      fromname: ....,
      waypointsPast: ...,
      toname: ...
  });
}

使用到的重构方法

  • 保持对象完整(319)
  • 引入参数对象(140)
  • 拆分循环(227)

提炼内联函数

因为每个接口请求的入参都不一样,所以在各自函数内添加内联函数处理。减少主代码体的行数,增加可读性。

export function getDirections(data) {
  ...
  ...
  ...
  // 获取接口请求参数
  // 提炼函数
  function _getParamsString({ coordinates, data }) {
    let paramObject = null;
    // let dataParams = null; // ?geometries=geojson&access_token=&alternatives=true&steps=true"
    paramObject = {
      // geometries: 'geojson',
      access_token: "",
      alternatives: true,
      steps: true,
      location: coordinates,
      coordtype: "sg_polyline"
    };
    //路线偏好设置,(系统推荐、最短路程,不给exclude参数),高速优先(exclude:toll),不做高速(exclude:motorway)
    if (data && data.exclude) {
      Object.assign(paramObject, {
        exclude: data.exclude
      });
    }
    return getParamString(paramObject); //将对象转换成字符串格式
  }
}

提炼后的代码结构

export function getDirections(data) {
  const coordinates = getCoordinates(
    store.state.directions,
    true,
    store.state.waypoint.waypointsPast
  );
  if (!data.first) {
    // 设置url参数,记录当前操作状态,页面刷新还原
    setUrlParams({
      type: "drive",
      state: store.state,
      data
    });
  }
  return new Promise((resolve, reject) => {
    fetch({
      url: `脱敏/driving/${coordinates}${_getParamsString({
        coordinates,
        data
      })}`,
      method: "get"
    })
      .then(response => {
        resolve(response);
      })
      .catch(error => {
        reject(error);
      });
  });

  // 获取接口请求参数
  // 提炼函数
  function _getParamsString({ coordinates, data }) {
    ...
  }
}

新增的功能

export function getDirectionsBus(data) {
  const { startAddress, endAddress } = data && data.queryPara ? data.queryPara : {};
  setUrlParams({
    type: "bus",
    state: store.state,
    data
  });
  return new Promise((resolve, reject) => {
    fetch({
      url: `脱敏/integrated${_getParamsString({
        startAddress,
        endAddress
      })}`,
      method: "get"
    })
      .then(response => {
        resolve(response);
      })
      .catch(error => {
        reject(error);
      });
  });

  // 获取接口请求参数
  // 提炼函数
  function _getParamsString({ startAddress, endAddress }) {
	 ...
  }
}

代码结构

  1. getCoordinates
  2. setUrlParams
  3. _getParamsString 拼凑接口参数,每个接口不一致,内联函数提取
  4. 处理接口

结束

代码肯定还有很多可以优化和重构的地方,今天先把书上看到用得上的方法在实际代码里面实践一下。实践不仅理清了原代码的逻辑,还简洁了函数的结构。

你可能感兴趣的:(优化,代码重构)