如何重构一些可怕的代码

开发 前端
重构本身并不总是合理的。 但是,很难想象有人会抱怨增加测试范围,尤其是在涵盖如此关键的功能的情况下。

最近,我不得不重构一些处于粗糙状态的代码:

  1. function endpoint(service, version = '') { 
  2.   let protocol 
  3.   let domain 
  4.  
  5.   if (service === 'webclient') { 
  6.     protocol = __CLIENT_PROTOCOL__ 
  7.     domain = `${__ENV__}-${service}.${__DOMAIN__}` 
  8.     if (__SERVER__) { 
  9.       protocol = 'http' 
  10.     } else if (__ENV__ === 'production') { 
  11.       domain = `www.${__DOMAIN__}` 
  12.     } 
  13.   } else { 
  14.     if (__ENV__ !== 'local') { 
  15.       if (__SERVER__) { 
  16.         protocol = 'http' 
  17.         domain = `${__ENV__}-${service}` 
  18.         domain += `.${__DOMAIN__}` 
  19.       } else { 
  20.         protocol = __CLIENT_PROTOCOL__ 
  21.         domain = `${__ENV__}-api.${__DOMAIN__}` 
  22.         if (service !== 'core') { 
  23.           domain += `/${service}` 
  24.         } 
  25.         if (version) { 
  26.           domain += `/${version}` 
  27.         } 
  28.       } 
  29.     } else { 
  30.       protocol = 'http' 
  31.  
  32.       if (service === 'core') { 
  33.         if (__CLIENT__) { 
  34.           domain = `api.${__DOMAIN__}` 
  35.         } else { 
  36.           domain = `api.${__DOMAIN__}:80` 
  37.         } 
  38.       } else { 
  39.         if (__CLIENT__) { 
  40.           domain = `api.${__DOMAIN__}/${service}/${version}` 
  41.         } else { 
  42.           domain = `api.${__DOMAIN__}:80/${service}/${version}` 
  43.         } 
  44.       } 
  45.     } 
  46.   } 
  47.  
  48.   const url = `${protocol}://${domain}` 
  49.  
  50.   return url 
  51.  
  52. export default endpoint 

上面的逻辑确定端点的URL,它取决于多种因素:您使用的服务,在服务器或客户端上呈现的服务,在生产环境或测试环境中使用的服务等。

一段代码变得如此混乱的原因之一是,当我们忘记重复比错误的抽象要便宜得多时。

好消息是,您可以应用几种简单的技术来简化嵌套的if-else语句。 坏消息是,这段代码对应用程序至关重要,因为所有请求都在调用它。 哦,它也没有经过测试!

所以,这就是我重构的方式…

1. 给该代码100%的测试覆盖率

我没有执行重构代码的任务,所以打电话了。 但是我不知道要花多少时间(我本该用来交付其他东西的时间)。

重构本身并不总是合理的。 但是,很难想象有人会抱怨增加测试范围,尤其是在涵盖如此关键的功能的情况下。 因此,我决定从此开始,不仅是为了让我对重构充满信心,而且因为当我完成测试时,我将更好地了解重构的难度,并且我可以进行/不进行。 去打电话。

如今,我们大多数人都在Gousto中实践TDD,但是代码库的这一特定部分是几年前的,它的重要性和状态阻碍了我和其他人在过去对其进行重构。

TDD的主要好处是无需担心,也无需花费成本即可进行重构-罗伯特·马丁(Robert C. Martin,鲍勃叔叔)

我还想确保覆盖率是100%,所以我使用了Jest标志–coverage,它提供了以下输出:

  1. -------------------|----------|----------|----------|----------|-------------------| 
  2. File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s | 
  3. -------------------|----------|----------|----------|----------|-------------------| 
  4.   ...              |      ... |      ... |      ... |      ... |                ...| 
  5.   endpoint.js      |      100 |      100 |      100 |      100 |                   | 
  6.   ...              |      ... |      ... |      ... |      ... |                ...| 
  7. -------------------|----------|----------|----------|----------|-------------------| 
  8. Test Suites: 1 passed, 1 total 
  9. Tests:       12 passed, 12 total 

2. 提取功能

现在我们有了测试,有了更多的信心,我们可以开始拆分代码。 让我们从头开始。 我们看到,根据服务,环境,客户端/服务器端……为协议分配了不同的值,然后将其添加到函数末尾的其余url中。

  1. const url = `${protocol}://${domain}` 

因此,我们可以将确定协议的代码抽象为自己的函数,只需调用一次即可:

  1. import endpoint from 'config/endpoint' 
  2.  
  3. describe('endpoint.js', () => { 
  4.   global.__DOMAIN__ = 'gousto.local' 
  5.   let service 
  6.  
  7.   describe('when the service is "webclient"', () => { 
  8.     beforeEach(() => { 
  9.       service = 'webclient' 
  10.     }) 
  11.  
  12.     describe('and being in the server side', () => { 
  13.       beforeEach(() => { 
  14.         global.__SERVER__ = true 
  15.         global.__ENV__ = 'whateverenv' 
  16.       }) 
  17.  
  18.       test('an http address with the corresponding ENV, SERVICE and DOMAIN is returned', () => { 
  19.         const url = endpoint(service) 
  20.         expect(url).toBe(`http://${__ENV__}-${service}.${__DOMAIN__}`) 
  21.       }) 
  22.     }) 
  23.  
  24.     describe('and not being in the server side', () => { 
  25.       ... 
  26.       describe('and the environment is production', () => { 
  27.         ... 
  28.         test('an https address with "www" and without the service, but with the DOMAIN is returned', () => {...}) 
  29.       }) 
  30.  
  31.       describe('and the environment is not production', () => { 
  32.         ... 
  33.         test('an https address with the corresponding ENV, SERVICE and DOMAIN is returned', () => {...}) 
  34.       }) 
  35.     }) 
  36.   }) 
  37.  
  38.   describe('when the service is not "webclient"', () => { 
  39.     ... 
  40.     describe('and the env is not "local"', () => { 
  41.       ... 
  42.       describe('and being in the server side', () => { 
  43.         ... 
  44.         test('an http address with the corresponding ENV, SERVICE and DOMAIN is returned', () => {...}) 
  45.       }) 
  46.  
  47.       describe('and not being in the server side', () => { 
  48.         ... 
  49.         describe('and the service is core', () => { 
  50.           ... 
  51.           test('an https API address with the corresponding ENV and DOMAIN is returned', () => {...}) 
  52.  
  53.           describe('and a version was passed', () => { 
  54.             test('an https API address with the corresponding ENV, DOMAIN, SERVICE and VERSION is returned', () => {...}) 
  55.           }) 
  56.         }) 
  57.  
  58.         describe('and a version was passed', () => { 
  59.           test('an https API address with the corresponding ENV, DOMAIN, SERVICE and VERSION is returned', () => {...}) 
  60.         }) 
  61.  
  62.         describe('and a version was not passed', () => { 
  63.           test('an https API address with the corresponding ENV, DOMAIN and SERVICE is returned', () => {...}) 
  64.         }) 
  65.       }) 
  66.     }) 
  67.  
  68.     describe('and the env is "local"', () => { 
  69.       ... 
  70.       describe('and the service is core', () => { 
  71.         ... 
  72.         describe('and being in the client side', () => { 
  73.           ... 
  74.           test('an http API address with the corresponding DOMAIN is returned', () => {...}) 
  75.         }) 
  76.  
  77.         describe('and not being in the client side', () => { 
  78.           ... 
  79.           test('an http API address with the corresponding DOMAIN and port 80 is returned', () => {...}) 
  80.         }) 
  81.       }) 
  82.  
  83.       describe('and the service is not core', () => { 
  84.         ... 
  85.         describe('and being in the client side', () => { 
  86.           ... 
  87.           test('an http API address with the corresponding DOMAIN, SERVICE and VERSION is returned', () => {...}) 
  88.         }) 
  89.  
  90.         describe('and not being in the client side', () => { 
  91.           ... 
  92.           test('an http API address with the corresponding DOMAIN, port 80, SERVICE and VERSION is returned', () => {...}) 
  93.         }) 
  94.       }) 
  95.     }) 
  96.   }) 
  97. }) 

我们可以对URL的其余部分执行与getProtocol()相同的操作。 提取的功能越多,if-else的地狱就越容易简化,提取剩余的内容就越容易。 因此,我的建议是从最小的复杂度入手,因为这将使其余的操作更为简单。

提取这些函数并不复杂,但这是因为我将if-else的噩梦翻译成了它们。 因此,我们没有大的混乱,但有一些小混乱。 不能接受 让我们来照顾它。

3. 简化

除了关注点分离之外,将url的不同部分提取为不同功能的优点是可以进一步简化条件。 之前,URL的某些部分限制了简化,因为它们需要满足多个条件。 现在,它们是分开的,所以我们不必为此担心。

您可以只是出于对它的关注而进行简化……或者您可以使用真值表来提供帮助。

对于getProtocol(),一种真值表如下所示:

但可以简化一下("-"表示该值无关紧要):

我们只有两个可能的值:http和https,因此我们可以采用较少行(https)的值,检查条件,然后返回另一个(http)。

  1. const getProtocol = (service, isServerSide, environment) => { 
  2.   if (service === 'webclient' && !isServerSide) { 
  3.     return 'https' 
  4.   } 
  5.    
  6.   if (service !== 'webclient' && !isServerSide && environment !== 'local') { 
  7.    return 'https'  
  8.   } 
  9.    
  10.   return 'http' 

这已经比上一节中的要好。 但是我们仍然可以做更多! 检查前两个条件,您可能会发现只有在我们不在服务器端时才获得https。 因此,我们可以为http编写第一个条件,并在函数的其余部分中摆脱isServerSide:

  1. const getProtocol = (service, isServerSide, environment) => { 
  2.   if (isServerSide) { 
  3.     return 'http' 
  4.   } 
  5.    
  6.   if (service === 'webclient') { 
  7.     return 'https' 
  8.   } 
  9.    
  10.   if (service !== 'webclient' && environment !== 'local') { 
  11.    return 'https'  
  12.   } 
  13.    
  14.   return 'http' 

不过,我们可以做得更好。 现在我们可以合并第二个和第三个条件,因为它们较小:

  1. const getProtocol = (service, isServerSide, environment) => { 
  2.   ... 
  3.   if (service === 'webclient' ||  
  4.       (service !== 'webclient' && environment !== 'local')) { 
  5.     return 'https' 
  6.   } 
  7.   ... 

但是,请保持……这种状况有点愚蠢,不是吗? 如果服务是webclient,则条件为true。 否则……转到OR的第二部分,为什么我们需要检查服务是否不是webclient? 如果我们在OR的右侧,那么我们确定它不是webclient。 最终结果看起来很整洁,特别是与第2部分中的原始结果相比。提取功能:

  1. const getProtocol = (service, isServerSide, environment) => { 
  2.   if (isServerSide) { 
  3.     return 'http' 
  4.   } 
  5.  
  6.   if (service === 'webclient' || environment !== 'local') { 
  7.     return 'https' 
  8.   } 
  9.  
  10.   return 'http' 

结果

多亏了功能的提取,我们最终有了一个endpoint()函数,这是我写的荣幸。

  1. ... 
  2.  
  3. function endpoint(service, version = '') { 
  4.   const protocol = getProtocol(service, __SERVER__, __ENV__) 
  5.   const subdomain = getSubdomain(service, __SERVER__, __ENV__) 
  6.   const path = getPath(service, __SERVER__, __ENV__, version) 
  7.   const port = getPort(service, __ENV__, __CLIENT__) 
  8.  
  9.   return `${protocol}://${subdomain}.${__DOMAIN__}${port}${path}` 
  10.  
  11. export default endpoint 

上面代码的get函数很小,足以被理解而不会动您的大脑。 并非所有人都像我想要的那样简单,但是它们比原始代码要好得多。 您可以在这里看到文件的外观。

最后,您应该始终尝试遵循以下规则:让代码比发现的要好。 尽管赞美❤️不要这么做

> GitHub comment in the PR

> A team mate making my day

现在轮到您了,您是否有任何想法使它变得更简单(无需更改在其余应用程序中的使用方式)? 您对重构的处理方式会有所不同吗?

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2009-11-26 10:32:57

PHP代码优化

2020-09-21 06:58:56

TS 代码建议

2009-06-22 15:36:00

如何学好java

2012-12-24 14:51:02

iOS

2021-10-31 07:36:17

前端JavaScript编程

2019-08-01 12:56:00

Python代码记录

2023-03-09 17:54:04

2013-03-29 09:03:59

iOS实用小代码iOS开发

2009-08-04 16:06:19

ASP.NET代码分离

2015-10-12 08:59:57

异步代码测试

2012-09-11 09:16:52

Hadoop

2011-06-01 16:50:21

JAVA

2009-06-25 09:50:32

JSF

2010-09-28 14:14:19

SQL语句

2013-07-02 10:18:20

编程编程策略

2013-07-02 09:43:02

编程策略

2020-02-03 16:03:36

疫情思考

2016-11-16 21:18:42

android日志

2009-09-21 17:46:25

Hibernate数据

2009-06-18 14:54:52

Spring AOP
点赞
收藏

51CTO技术栈公众号