互联网裁员潮下,教你如何写出在公司不可替代的代码!!

前言

本文是学习《重构:改善既有代码的设计》后的一些心得,希望能用趣味的方式结合一些实例带领大家一起学习,提升自身代码质量。

想必最近的互联网裁员消息大家也有所耳闻,那么我们怎么才能够在这样的大环境下苟住自身呢?经过我的总结,我认为大家都不具备不可替换性

什么叫不可替换性呢,通俗点来说就是,这活除了你别人都干不了。达到这种境界无异于两种情况,一种是自身过于优秀,优秀到没人能取代(该情况过少,希望大家能正视己身)。

另一种方法则是,制作出专属于你的代码!!下面我们来一起学习,怎样写出专属于你,不可被替代的代码!

以下不可替换写法皆为反面教材!!!

一、神秘命名(Mysterious Name)

命名让人猜不透,摸不准!

不可替换写法

const getPNum = (number) => {
  ......
}

无论是函数命名还是入参命名,相信都很难有人能参透你的深意,在别人接手你的代码时,必定会来向你请教,这在老板眼里你的价值将更为突出。

正常写法

const getPhoneCode = (phoneNumber) => {
  ......
}

从函数的驼峰命名我们可以很轻易猜出是获取手机验证码,入参也能猜出是手机号码的意思,这样的代码太通俗易懂了,显然达不到我们的效果。

二、重复代码(Duplicated Code)&& 过长函数(Long Function)

重复编写大量相同代码,内容过多的函数,使代码变得臃肿难以维护

不可替换写法

const showUserInfo = () => {
  let totalAmount = 0;
  const userInfo = request.get('/userInfo', 'admin')
  const userAmountList = request.get('/userAmountList', 'admin')
  console.log('name', userInfo.name);
  console.log('age', userInfo.age);
  console.log('sex', userInfo.sex);
  console.log('address', userInfo.address);
  for(let i of userAmountList) {
    totalAmount += i.amount
  }
  console.log('总金额', totalAmount);
}

大量重复的代码让人瞬间产生疲劳感,完全不搭边的代码顺序混淆人的双眼,如果再加上一些神秘命名,必将让代码更上一个台阶。

正常写法

const showUserInfo = () => {
  const printUserDetail = (userInfo) => {
    const { name, age, sex, address } = userInfo;
    console.log('name', name);
    console.log('age', age);
    console.log('sex', sex);
    console.log('address', address);

  const printTotalAmount = (userAmountList) => {
    const totalAmount = userList.reduce((pre, cur) => pre + cur.amount, 0)
    console.log('总金额', totalAmount);
  }
  
  // 获取用户信息
  const userInfo = request.get('/userInfo', 'admin')
  printUserDetail(userInfo)
  
  // 获取用户金额列表
  const userAmountList = request.get('/userAmountList', 'admin')
  printTotalAmount(userAmountList)
}

重复代码都被提炼到单独的函数模块中,用reduce免去了重复的代码相加,并且代码顺序也被移动至有关联的地方,这样的代码换做刚学前端的小白恐怕也能看懂,这样明显不能凸显自身的独特。

三、过长参数列表(Long Parameter List)

函数或组件的参数过多,影响代码可读性,某些环境甚至会对性能造成影响

不可替换写法

const getList = (id, name, age, address, sex) => {
  ...
}

将所有参数全部写进入参,当入参达到十几甚至上百时,你的函数定会震慑住你所有的同事。

正常写法

const getList = (data) => {
  const { id, name, age, address, sex } = data;
  ...
}

将入参放置到一个对象中,再到函数里通过解构的方式进行调用。这样的方式太过简洁,过少的入参凸显不出你这个函数的重要性。

四、全局数据(Global Data)

将数据全部挂载到全局,导致内存不及时被释放以及全局污染

不可替代写法

const id = 1;
const data1 = request.get('/userInfo', id)
const data2 = request.get('/userState', id)

const getUserInfo = () => {
  ...
}

const getUserState = () => {
  ...
}

所有变量放入全局,把后续开发者的路变窄,不敢随意去更改变量,此刻再加上神秘命名,相信没有人能够取代你的位置。

正常写法

const id = 1;

const getUserInfo = () => {
  const data = request.get('/userInfo', id)
  ...
}

const getUserState = () => {
  const data = request.get('/userState', id)
  ...
}

id作为多处用到变量,写到全局,剩下的局部变量都写在各自函数中,即不会引起全局污染,也不会担心命名重复的问题。在各自的作用域中作用也清晰明了。

五、发散式变化(Divergent Change)

将需要做的事分散到各个地方,每次修改需要修改对应函数,修改不当会导致另一个依赖此函数的功能崩塌

不可替代写法

const getPrice = (list) => {

  const printName = (item) => {
    if(item.type === 'totalList') {
      console.log('totalName', item.name);
    }else if(item.type === 'frozenList'){
      console.log('frozenName', item.name);
    }
  }
  
  const calcPrice = (item) => {
    if(item.type === 'totalList') {
      // todo: 计算totalPrice
      const price = ...;
      return price;
    }else if(item.type === 'frozenList'){
      // todo: 计算frozenPrice
      const price = ...;
      return price;
    }
  }
  
  printName(list.totalList);
  printName(list.frozenList);
  return calcPrice(list.totalList) - calcPrice(list.frozenList)
}

将方法写成公用方法,在每次修改或者新增时候都需要去修改对应的方法。无法知道每个价格对应着哪些操作,当增加一个新的价格类型时,需要同时去多个函数中添加对应的判断逻辑。一不注意就会忘加漏加形成bug。测试绩效max!

正常写法

const getPrice = (list) => {
  const totalPrice = (item) => {
    // todo: 计算totalPrice
    const price = ...
    console.log('totalName', item.name);
    console.log('price', price);
  }
  
  const frozenPrice = (item) => {
    // todo: 计算frozenPrice
    const price = ...
    console.log('frozenName', item.name);
    console.log('price', price);
  }
  
  return totalPrice(list.totalList) - frozenPrice(list.frozenList)
}

每个价格对应需要的操作都被提炼到单独的函数,专注于负责自己的事,如果价格计算方式需要改变,可以更加直观的修改。若需要添加新的价格品种,也将会更好添加。

六、霰弹式修改(Shotgun Surgery)

多处共用一个属性,不设置全局变量管理,每次修改需要修改大量代码

不可替代写法

getList(globalModel.id)
getUserInfo(globalModel.id)
getUserAmount(globalModel.id)

当需求改变,需要在多处进行修改,这样的工作量倍增,会让你的工作力max!

正常写法

const id = globalModel.id;
    
getList(id)
getUserInfo(id)
getUserAmount(id)

同一个属性被多处使用,使用一个变量进行存储,当需求发生改变(例如globalModel.id变为globalModel.userId),只需要修改一处便能完成,大大节省时间。

七、依恋情结(Feature Envy)

大量引入其他函数或模块方法,导致代码耦合度极高,动一处则牵扯全身

不可替代写法

class Price {
  constructor() {}
  
  add(...num) {
    return num.reduce((pre, cur) => pre + cur, 0);
  }
  
  dataFilter(value) {
    return parseInt(value.substring(0, value.length - 2)) * 100;
  }
}

class Amount {
  constructor() {}
  
  getAmount(amountList) {
    const _amountList = amountList.map(item => {
      return new Price().dataFilter(item);
    });
    return new Price().add(..._amountList);
  }
}

所有的计算函数全部使用其他类里的方法,形成大量依赖。你要出事我跟着一起死,我就是要用你的,我就是玩~

正常写法

class Amount {
  constructor() {}
  
  add(...num) {
    return num.reduce((pre, cur) => pre + cur, 0);
  }
  
  dataFilter(value) {
    return parseInt(value.substring(0, value.length - 2)) * 100;
  }
  
  getAmount(amountList) {
    const _amountList = amountList.map(item => {
      return this.dataFilter(item);
    });
    return this.add(..._amountList);
  }
}

类里所有使用的方法都在本身完成,所有的问题都在自身解决,形成闭环

八、数据泥团(Data Clumps)

众多数据糅合在一起,当其中某一项数据失去意义时,其他项数据也失去意义。

不可替代写法

const lastName = "卢"
const firstName = "本伟"
const name = `${lastName}${firstName}`

发现当其中某个变量失去意义的时候,另一个变量也失去意义,一损俱损。

正常写法

const person = {
  lastName: "卢",
  firstName: "本伟"
}
const name = `${lastName}${firstName}`

有强联系的数据,应为它们产生一个新对象。

九、基本类型偏执(Primitive Obsession)

认为基本类型一定更加简单,偏执的使用大量的基本类型而不去定义应有的结构

不可替代写法

class Price {
  constructor(name, money) {
    this.name = name;
    this.money = money;
  }
  get name() {
    return name;
  }
  get count() {
    return parseFloat(this.money.slice(1));
  }
  get current() {
    return this.money.slice(0, 1);
  }
  get unit() {
    switch (this.money.slice(0, 1)) {
      case '¥':
        return 'CNY';
      case '$':
        return 'USD';
      case 'k':
        return 'HKD';
    }
  }
  calcPrice() {
    // todo: 金额换算
  }
}
const myPrice = new Price("罐头", "$30")

偏执地使用字符串基本类型定义money,表面是Price的类,但在里面充斥着大量的money的数据处理。

正常写法

class Money {
  constructor(value) {
    this.value = value;
  }
  get count() {
    return parseFloat(this.value.slice(1));
  }
  get current() {
    return this.value.slice(0, 1);
  }
  get unit() {
    switch (this.value.slice(0, 1)) {
      case '¥':
        return 'CNY';
      case '$':
        return 'USD';
      case 'k':
        return 'HKD';
    }
  }
}
class Price {
  constructor(name, money) {
    this.name = name;
    this.money = new Money(money);
  }
  get name() {
    return name;
  }
  calcPrice() {
    // todo: 金额换算
  }
}
const myPrice = new Price("罐头", "$20")

money中存在着大量的数据处理,应为期单独建立个对象来作为它的类型。

十、重复的switch(Repeated Switches)

大量使用重复逻辑的switch,这样的代码是臃肿且脆弱的

不可替代写法

class Money {
  constructor(value) {
    this.value = value;
  }
  get count() {
    return parseFloat(this.value.slice(1));
  }
  get current() {
    return this.value.slice(0, 1);
  }
  get unit() {
    switch (this.current) {
      case '¥':
        return 'CNY';
      case '$':
        return 'USD';
      case 'k':
        return 'HKD';
    }
  }
  get suffix() {
    switch (this.current) {
      case '¥':
        return '元';
      case '$':
        return '美元';
      case 'k':
        return '港币';
    }
  }
  get currentCount() {
    switch (this.current) {
      case '¥':
        return this.count;
      case '$':
        return this.count * 7;
      case 'k':
        return this.count * 0.8;
    }
  }
}

大量相同逻辑的switch,若想增加一个判断项,需找到所有switch项进行修改,一不注意则会遗漏,引发bug

正常写法

class Money {
  constructor(value) {
    this.value = value;
  }
  get count() {
    return parseFloat(this.value.slice(1));
  }
  get current() {
    return this.value.slice(0, 1);
  }
}
class cnyMoney extends Money {
  constructor(props) {
    super(props);
  }
  get unit() {
    return 'CNY';
  }
  get suffix() {
    return '元';
  }
  get currentCount() {
    return this.count;
  }
}
class usdMoney extends Money {
  constructor(props) {
    super(props);
  }
  get unit() {
    return 'USD';
  }
  get suffix() {
    return '美元';
  }
  get currentCount() {
    return this.count * 7;
  }
}
class hkdMoney extends Money {
  constructor(props) {
    super(props);
  }
  get unit() {
    return 'HKD';
  }
  get suffix() {
    return '港币';
  }
  get currentCount() {
    return this.count * 0.8;
  }
}

每一个分支项专注于自身的变化,修改时不会担心某处遗漏。

十一、循环语句(Loops)

舍弃ES自带api,全面拥抱for、while循环语句

不可替代写法

const arr = [1, 2, 3, 4, 5, 6];
// 找到数组指定项
for (let i of arr) {
  if (i === 3) {
    console.log(i);
  }
}
// 数组求和
let sum = 0;
for (let i of arr) {
  sum += i;
}
console.log(sum);

什么ES6,算法我都不用,就for循环一把梭!

正常写法

const arr = [1, 2, 3, 4, 5, 6];
// 找到数组指定项
console.log(arr.find((item) => item === 3));
// 数组求和
let sum = arr.reduce((pre, cur) => pre + cur);
console.log(sum);

巧用各类api,提升代码整洁度。

十二、冗赘的元素(Lazy Element)

单一的功能也会提炼成相应函数,即使它的实现代码和函数名基本一样。

不可替代写法

const getUserInfo = (user) => {
  const info = {};
  const getName = (data) => {
    const firstName = data.firstName;
    const lastName = data.lastName;
    return firstName + lastName;
  }
  info.name = getName(user)
  // todo: 获取用户其他信息
  ...
  return info;
}

获取姓名,获取地址,哪怕只是简单的字符串相加我也要提炼成函数!面向函数编程!

正常写法

const getUserInfo = (user) => {
  const info = {};
  info.name = user.firstName + user.lastName
  // todo: 获取用户其他信息
  ...
  return info;
}

区分方法和命令的区别,能用命令解决的事不要多此一举。

十三、夸夸其谈通用性(Speculative Generality)

当我们设计代码时总会提前设计好一些结构,想着未来有一天会有新功能添加,但事实上,这一天永远不会到来。

不可替代写法

const getCampaign = (campaign, type) => {
  switch (type) {
    case 1:
      // todo: type为1的处理
      return ...
    default:
      // todo: 正常处理
      return Campaign;
  }
}

认为以后可能会对该包进行一些特殊处理,设置一个type进行判断,增加"通用性"。

正常写法

const getCampaign = (campaign) => {
  // todo: 处理方案包
  return campaign;
}

只处理当前需求所需要做的事,后面的事后面再说。

十四、临时字段(Temporary Field)

一个类中为了某种特定情况设定一个特殊字段,但除了那种情况外这个字段永远不会被使用。

不可替代写法

class Person{
  constructor(data) {
    this.name = data.name;
    this.age = data.age;
    this.address = data.address;
    // 下列两个变量只有在进行续费处理时才会使用
    this.balance = data.balance;
    this.isRenew = data.isRenew;
  }
  ...
}

所有特殊情况需要用到的字段全往类里塞,当被使用时对这些字段产生疑惑,并给予强行赋值。

正常写法

class Person{
  constructor(data) {
    this.name = data.name;
    this.age = data.age;
    this.address = data.address;
  }
  ...
}
class RenewData {
  constructor() {
    this.balance = data.balance;
    this.isRenew = data.isRenew;
  }
}

将特殊情况字段提取出来,使用时再进行调用。

十五、过长的消息链(Message Chains)

A函数访问B函数,B函数访问C函数或变量,这样的代码会形成长长的消息链。

不可替代写法

const data = getData(getName(getiId(cookie)));

多层函数进行嵌套,耦合度更高,且无法理解函数这样组合的目的。

正常写法

const getPackage = (cookie) => {
  const id = getId(cookie);
  const name = getName(id);
  return getData(name);
}

const data = getPackage(cookie);

将方法提炼成函数,并将每一步解脱出来。

十六、中间人(Middle Man)

将所有操作委托给另一个类,但事实上这一层是没有必要的。

不可替代写法

class Person {
  constructor(data) {
    this.balance = data.balance;
    this.info = data.info;
    this.id = data.id;
  }
  get name() {
    return this.info.name;
  }
  get address() {
    return this.info.address;
  }
  get age() {
    return this.info.age;
  }
}

info里的每一项获取都通过Person类代理,过多代理会让这个类变得十分臃肿。

正常写法

class Person {
  constructor(data) {
    this.balance = data.balance;
    this.info = data.info;
    this.id = data.id;
  }
  get info() {
    return this.info;
  }
}

免去Person来代理info里的变量获取,更加清晰明了。

十七、内幕交易(Insider Trading)

在类的内部有着与其他类的数据交换,调用者在调用时需要去了解其他接口的细节,增加类的耦合

不可替代写法

class Person {
  constructor(data) {
    this.name = name;
  }
  get name() {
    return name;
  }
  set package(arg) {
    this.package = arg;
  }
  get package() {
    return this.package;
  }
}

class Package {
  constructor(name) {
    this.name = name;
  }
  set id(arg) {
    this.id = arg;
  }
  get id() {
    return this.id;
  }
}

要访问person的packageId时需要与Package类进行数据交换,了解Package的内部细节。

正常写法

class Person {
  constructor(data, package) {
    this.name = name;
    this.package = package;
  }
  get name() {
    return name;
  }
  get packageId() {
    return this.package.id;
  }
}

class Package {
  constructor(name) {
    this.name = name;
  }
  set id(arg) {
    this.id = arg;
  }
  get id() {
    return this.id;
  }
}

将内部的数据交换直接引入到Person类,搬到明面上进行处理

十八、过大的类(Insider Trading)

所有与该类相关的字段全部放入类中,显得类十分的庞大臃肿。

不可替代写法

class Product {
  constructor(data) {
    this.name = data.name;
    this.price = data.price;
    this.id = data.id;
    this.user = data.user;
    this.uv = data.uv;
    this.pv = data.pv;
    this.click = data.click;
  }
  get realPrice() {
    return this.price * this.discount;
  }
  get ctr() {
    return this.uv / this.click;
  }
}

商品的所有相关字段全部放入,复杂的场景中甚至能达到几十上百个字段。

正常写法

class Product {
  constructor(data) {
    this.name = data.name;
    this.id = data.id;
    this.user = data.user;
  }
}

class ProductPrice {
  constructor(data) {
    this.price = data.price;
    this.discount = data.discount;
  }
  get realPrice() {
    return this.price * this.discount;
  }
}

class ProductData {
  constructor(data) {
    this.uv = data.uv;
    this.pv = data.pv;
    this.click = data.click;
  }
  get ctr() {
    return this.uv / this.click;
  }
}

将内部强相关的字段提炼成一个新的类,会让结构更加清晰。

十九、异曲同工的类(Alternative Classes with Different Interfaces)

类的好处之一就是可以替换,但要避免出现做相似事情的类。

不可替代写法

class AmericanCompany {
  constructor(name, createTime, address, phone, isLegitimate) {
    this.name = name;
    this.createTime = createTime;
    this.address = address;
    this.phone = phone;
    this.isLegitimate = legitimate;
  }
  get name() {
    return this.name;
  }
  get address() {
    return this.address;
  }
  get phone() {
    switch (this.address) {
      case '群島':
        return `+1-340${phone}`;
      default:
        return `+1${phone}`;
    }
  }
}
class ChineseCompany {
  constructor(name, createTime, address, phone, isOverTime) {
    this.name = name;
    this.createTime = createTime;
    this.address = address;
    this.phone = phone;
    this.isOverTime = isOverTime;
  }
  get name() {
    return this.name;
  }
  get address() {
    return this.address;
  }
  get phone() {
    switch (this.address) {
      case '香港':
        return `+852${phone}`;
      case '台湾':
        return `+886${phone}`;
      default:
        return `+86${phone}`;
    }
  }
}

美国公司和中国公司类的字段绝大部分和方法都是一样的,若字段更多...若还有其他国家...每一个都创建一个类,真是不可想象。

正常写法

class Company {
  constructor(name, createTime, address, phone) {
    this.name = name;
    this.createTime = createTime;
    this.address = address;
    this.phone = phone;
  }
  get name() {
    return this.name;
  }
  get address() {
    return this.address;
  }
}
class AmericanCompany extends Company {
  constructor(name, createTime, address, phone, isLegitimate) {
    super(name, createTime, address, phone);
    this.isLegitimate = legitimate;
  }
  get phone() {
    switch (this.address) {
      case '群島':
        return `+1-340${this.phone}`;
      default:
        return `+1${this.phone}`;
    }
  }
}
class ChineseCompany extends Company {
  constructor(name, createTime, address, phone, isOverTime) {
    super(name, createTime, address, phone);
    this.isOverTime = isOverTime;
  }
  get phone() {
    switch (this.address) {
      case '香港':
        return `+852${this.phone}`;
      case '台湾':
        return `+886${this.phone}`;
      default:
        return `+86${this.phone}`;
    }
  }
}

将重复部分提炼成一个类,再分别继承。

二十、纯数据类(Data Class)

一个类中只有单纯的存储数据的作用,只能进行简单读取功能。

不可替代写法

class Person {
  constructor(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
  }
  get name() {
    return this.name;
  }
  get age() {
    return this.age;
  }
  get address() {
    return this.address;
  }
}

哪怕只是简单的存储,我也要制作一个类!面向类编程!

正常写法

const person = {
......
}

或者

class Person {
  constructor(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
  }
  get name() {
    return this.name;
  }
  get age() {
    return this.age;
  }
  get address() {
    return this.address;
  }
  // todo: 一些与该类有关的处理方法
}

单纯的数据类使用对象去承载便可,如果有必要使用类,请将相关函数方法搬至类的内部。

二十一、被拒绝的馈赠(Refused Bequest)

父类中某个字段只被一个或极少数的继承类继承,其他的类并不需要这个字段。

不可替代写法

class Company {
  constructor(name, createTime, address, phone, isOverTime) {
    this.name = name;
    this.createTime = createTime;
    this.address = address;
    this.phone = phone;
    this.isOverTime = isOverTime;
  }
  // todo: 相关方法
  ...
}
class AmericanCompany extends Company {
  constructor(name, createTime, address, phone, isLegitimate) {
    super(name, createTime, address, phone);
    this.isLegitimate = legitimate;
  }
  ...
}
class ChineseCompany extends Company {
  constructor(name, createTime, address, phone, isOverTime) {
    super(name, createTime, address, phone, isOverTime);
  }
  // todo: 相关方法
  ...
}
class UKCompany extends Company {
  constructor(name, createTime, address, phone) {
    super(name, createTime, address, phone);
  }
  // todo: 相关方法
  ...
} 

即使父类Company中的isOverTime只被ChineseCompany使用,我也要给它加上!

正常写法

class Company {
  constructor(name, createTime, address, phone) {
    this.name = name;
    this.createTime = createTime;
    this.address = address;
    this.phone = phone;
  }
  // todo: 相关方法
  ...
}
class AmericanCompany extends Company {
  constructor(name, createTime, address, phone, isLegitimate) {
    super(name, createTime, address, phone);
    this.isLegitimate = legitimate;
  }
  ...
}
class ChineseCompany extends Company {
  constructor(name, createTime, address, phone, isOverTime) {
    super(name, createTime, address, phone);
    this.isOverTime = isOverTime;
  }
  // todo: 相关方法
  ...
}
class UKCompany extends Company {
  constructor(name, createTime, address, phone) {
    super(name, createTime, address, phone);
  }
  // todo: 相关方法
  ...
} 

将专属字段isOverTime下沉至ChineseCompany中,自己的事自己做!

二十二、注释(Comments)

注释是一个双刃剑,好的注释能更好地帮助理解代码,但过于依赖注释会让人放弃对代码质量的要求。

不可替代写法

// 获取手机号码
const getPNum = () => {
  // todo: 相关操作
}

什么代码质量我全都不管,反正我有注释,别人看得懂。

正常写法

const getPhoneNumber = () => {
  // todo: 相关操作
}

注释不是我们的挡箭牌,而是我们最后一道防线,当你觉得需要用注释的时候,请再思考代码是否还有可以优化的地方。

总结

你见过的最不可替代的代码是什么样的呢?

猜你喜欢

转载自juejin.im/post/7126888773647876110