我最近不得不重构一些处于粗糙状态的代码:functionendpoint(service,version=''){letprotocolletdomainif(service==='webclient'){protocol=__CLIENT_PROTOCOL__domain=`${__ENV__}-${service}.${__DOMAIN__}`if(__SERVER__){protocol='http'}elseif(__ENV__==='production'){domain=`www.${__DOMAIN__}`}}else{if(__ENV__!=='local'){if(__SERVER__){protocol='http'domain=`${__ENV__}-${service}`domain+=`.${__DOMAIN__}`}else{protocol=__CLIENT_PROTOCOL__domain=`${__ENV__}-api.${__DOMAIN__}`if(service!=='core'){domain+=`/${service}`}if(version){domain+=`/${version}`}}}else{protocol='http'if(service==='core'){if(__CLIENT__){domain=`api.${__DOMAIN__}`}else{domain=`api.${__DOMAIN__}:80`}}else{if(__CLIENT__){domain=`api.${__DOMAIN__}/${service}/${version}`}else{domain=`api.${__DOMAIN__}:80/${service}/${version}`}}}}consturl=`${protocol}://${domain}`returnurl}exportdefaultendpoint以上逻辑决定了端点的URL,它依赖于vario我们的因素:你使用的服务,在服务端或客户端呈现在客户端的服务,在生产环境或测试环境使用的服务等一段代码变得如此混乱的原因之一是当我们忘记这种重复比错误的抽象要便宜得多。好消息是您可以应用几种简单的技术来简化嵌套的if-else语句。坏消息是这段代码对应用程序至关重要,因为所有请求都在调用它。哦,它也没有经过测试!所以,这就是我重构的方式...1.为该代码提供100%的测试覆盖率我没有重构代码的任务,所以调用了。但我不知道这需要多少时间(我本可以花时间去交付其他东西)。重构本身并不总是合理的。但是,很难想象有人会抱怨测试覆盖率增加,尤其是在覆盖了如此关键的功能时。所以我决定从这里开始,不仅让我对重构更有信心,而且因为当我完成测试后,我会更好地了解重构有多难以及我能做什么/不做。打电话。这些天我们大多数人都在Gousto中实践TDD,但是代码库的这个特定部分已有几年历史,它的重要性和状态阻止了我和其他人在过去重构它。TDD的主要好处是能够无忧无虑地重构-RobertC.Martin(Bob大叔)我还想确保覆盖率是100%,所以我使用了Jest标志--coverage,它提供了以下输出:-------------------|------------|------------|----------|------------|--------------------|文件|%Stmts|%Branch|%Funcs|%Lines|UncoveredLine#s|--------------------|------------|----------|----------|------------|--------------------|...|...|...|...|...|...|endpoint.js|100|100|100|100||...|...|...|...|...|。..|--------------------|--------|------------|-----------|------------|--------------------|测试套件:1次通过,1次总测试:12次通过,12次总计2。现在提取函数我们已经进行了测试,而且更有信心,我们可以开始拆分代码了。让我们从头开始。我们看到协议根据服务、环境、客户端/服务器端被分配了不同的值……然后它被添加到函数末尾的url的其余部分。consturl=`${protocol}://${domain}`因此,我们可以将判断协议的代码抽象成自己的函数,只需要调用一次:importendpointfrom'config/endpoint'describe('endpoint.js',()=>{global.__DOMAIN__='gousto.local'letservicedescribe('whentheserviceis"webclient"',()=>{beforeEach(()=>{service='webclient'})describe('andbeingintheserverside',()=>{beforeEach(()=>{global.__SERVER__=trueglobal.__ENV__='whateverenv'})test('anhttpaddresswiththecorrespondingENV,SERVICEandDOMAINisreturned',()=>{consturl=endpoint(service)expect(url).toBe(`http://${__ENV__}-${service}.${__DOMAIN__}`)})})describe('andnotbeingintheserverside',()=>{...describe('andtheenvironmentisproduction',()=>{...test('带有“www”但没有服务的https地址,但返回了DOMAIN',()=>{...})})describe('并且环境不是生产环境',()=>{...test('返回了相应ENV、SERVICE和DOMAIN的https地址',()=>{...})})})})describe('当服务不是“webclient”时',()=>{...describe('andtheenvisnot"local"',()=>{...describe('andbeingintheserverside',()=>{...test('anhttpaddresswiththecorrespondingENV,SERVICEandDOMAINisreturned',()=>{...})})describe('andnotbeingintheserverside',()=>{...describe('andtheserviceiscore',()=>{...test('anhttpsAPIaddresswiththecorrespondingENVandDOMAINisreturned',()=>{...})describe('andaversionwaspassed',()=>{test('anhttpsAPIaddresswiththecorrespondingENV,DOMAIN,SERVICEandVERSIONisreturned',()=>{...})})})describe('andversionwaspassed',()=>{test('返回具有相应ENV、DOMAIN、SERVICE和VERSION的httpsAPI地址',()=>{...})})describe('版本未通过',()=>{test('返回具有相应ENV、DOMAIN和SERVICE的httpsAPI地址',()=>{...})})})})describe('andtheenvis"local"',()=>{...describe('andtheserviceiscore',()=>{...describe('andbeingintheclientside',()=>{...test('一个与对应的httpAPI地址ingDOMAINisreturned',()=>{...})})describe('andnotbeingintheclientside',()=>{...test('anhttpAPIaddresswiththecorrespondingDOMAINandport80isreturned',()=>{...})})})describe('andtheserviceisnotcore',()=>{...describe('andbeingintheclientside',()=>{...test('anhttpAPIaddresswiththecorrespondingDOMAIN,SERVICEandVERSIONisreturned',()=>{...})})describe('andnotbeingintheclientside',()=>{...test('anhttpAPIaddresswiththecorrespondingDOMAIN,port80,SERVICEandVERSIONisreturned',()=>{...})})})})})})我们可以对其余部分做同样的事情URL通过与getProtocol()相同的操作提取的功能越多,if-else地狱就越容易简化,剩下的也越容易提取。所以我的建议是从最简单的开始,因为它会让剩下的更容易。提取这些函数并不复杂,但这是因为我将if-else噩梦翻译成了它们。所以我们没有大混乱,但我们有一些小混乱。不能接受的。让我们来处理它。3.简化除了关注点分离之外,将URL的不同部分提取到不同的功能中的好处是可以进一步简化条件。以前,URL的某些部分限制了简化,因为它们需要满足多个条件。现在,它们是分开的,所以我们不必担心。您可以为了它而简化……或者您可以使用真值表来提供帮助。对于getProtocol(),一种真值表看起来是这样的:但它可以简化(“-”表示值无关紧要):我们只有两个可能的值:http和https,所以我们可以使用更少的行(https),检查条件,然后返回另一个(http)。constgetProtocol=(service,isServerSide,environment)=>{if(service==='webclient'&&!isServerSide){return'https'}if(service!=='webclient'&&!isServerSide&&environment!=='local'){return'https'}return'http'}这已经比上一节的要好。但我们仍然可以做得更多!检查前两个条件,你可能会发现我们只有在不在服务端的情况下才能获取到https。所以我们可以为http编写第一个条件,并在函数的其余部分去掉isServerSide:constgetProtocol=(service,isServerSide,environment)=>{if(isServerSide){return'http'}if(service==='webclient'){return'https'}if(service!=='webclient'&&environment!=='local'){return'https'}return'http'}但是,我们可以做得更好。现在我们可以结合第二个和第三个条件,因为它们较小:constgetProtocol=(service,isServerSide,environment)=>{...if(service==='webclient'||(service!=='webclient'&&environment!=='local')){return'https'}...}但是,坚持下去...这种情况有点傻,不是吗?如果服务是webclient,则条件为真。否则......转到OR的第二部分,为什么我们需要检查服务是否不是webclient?如果我们在OR的右侧,那么我们可以确定它不是webclient。最终结果看起来很整洁,尤其是与第2部分中的原始结果相比。提取函数:constgetProtocol=(service,isServerSide,environment)=>{if(isServerSide){return'http'}if(service==='webclient'||environment!=='local'){return'https'}return'http'}结果由于特征提取,我们最终得到了一个endpoint()函数,这是我编写的荣幸。...函数端点(服务,版本=''){constprotocol=getProtocol(服务,__SERVER__,__ENV__)constsubdomain=getSubdomain(服务,__SERVER__,__ENV__)constpath=getPath(服务,__SERVER__,__ENV__,版本)constport=getPort(服务,__ENV__,__CLIENT__)return`${protocol}://${subdomain}.${__DOMAIN__}${port}${path}`}exportdefaultendpoint上面代码的get函数足够小,不用动脑就能理解。并非所有的都像我想要的那样简单,但它们比原始代码要好得多。您可以在此处查看文件的外观。最后,您应该始终尝试遵循规则:让您的代码比您发现的更好。尽管赞美??不要这样做>GitHub在PR中发表评论>一个让我开心的队友现在轮到你了,你有什么想法可以让它更容易吗(不改变它在应用程序其余部分中的使用方式)?重构的处理方式会有所不同吗?
