├── .gitignore ├── LICENSE ├── README.md └── _config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | *.DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 xianweics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 重构 - 改善既有代码的设计 2 | 3 | ## 1 重构,第一个示例 4 | 5 | - 重构前,先检查自己是否有一套可靠的测试集。这些测试必须有自我验证能力。TDD 6 | - 重构技术就是以微小的步伐修改程序。如果犯下错误,很容易便可发现它。 7 | - 傻瓜都能写出计算机可以理解的代码。唯有能写出人类容易理解的代码的,才是优秀的程序员。 8 | - 编程时,需要遵循营地法则:保证你离开时,代码库一定比来的时候更健康。 9 | - 好代码验证的标准是人们是否能轻而易举的修改它。 10 | 11 | ## 2 重构的原则 12 | 13 | ### 2.1 何谓重构 14 | 15 | - 重构:对软件内部结构的一种调整,目的是在***不改变可观察行为的前提下***,提高其可理解性,降低其修改成本。 16 | - 重构的关键在于运用大量小且保证软件行为的步骤,一步步达到大规模的修改。 17 | - 如果有人说他们的代码在重构过程中有1-2天时间不可用,基本上可以确定,他们在做的事不是重构。 18 | 19 | ## 2.2 两顶帽子 20 | 21 | - 添加新功能,可能需要优化之前的程序结构;当功能开发好,也需要优化下程序的结构。不同的角色切换,是在添加功能过程中必不可少的步骤。 22 | 23 | ### 2.3 为何重构 24 | 25 | - 改进软件的设计 26 | - 使软件更容易理解 27 | - 帮助开发者找到bug 28 | - 提高编程速度 29 | 30 | ### 2.4 何时重构 31 | 32 | - 预备性:让添加新功能更容易 33 | - 帮助理解:使代码更易懂 34 | - 捡垃圾式重构 35 | - 有计划和见机行事的重构:肮脏的代码必须重构,但漂亮的代码也需要很多重构 36 | - 长期重构 37 | - 复审代码时重构 38 | - 何时不应该重构: 39 | - 只有当需要理解其工作原理时 40 | - 如果重写比重构容易 41 | 42 | ### 2.5 重构的挑战 43 | 44 | - 缓解新功能开发 45 | - 重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值 46 | - 重构应该总是由**经济利益**驱动,而不是在于把代码库打磨得闪闪发光 47 | - 分支 48 | - 持续集成,也叫基于主干开发,避免任何分支彼此差距太大,从而降低合并的难度 49 | - 测试 50 | - 自测试代码:快速发现错误 51 | - 遗留代码 52 | - 重构可以很好地帮助我们理解遗留系统,但遗留的系统大多数是没有测试。解决办法是:没测试就加测试。书籍推荐《修改代码的艺术》 53 | 54 | ### 2.6 重构、架构和YAGNI 55 | 56 | - 一旦代码写出来,架构就固定了,只会因为程序员的草率对待而逐渐腐败,重构可以改变这个状态 57 | - 重构可以应对未来的需求变化 58 | 59 | ### 2.7 重构与软件开发过程 60 | 61 | - 极限编程是最早的敏捷软件开发方法之一。要真正以敏捷的方式运作项目,团队成员必须在重构上有能力、有热情,他们采用的开发过程必须与常规的、持续的重构相匹配 62 | - 自测试代码 -> 持续集成 -> 重构 63 | 64 | ### 2.8 重构与性能 65 | 66 | - 除了对性能有严格要求的实时系统,其他情况下,“编写快速软件”的秘诀是:先写出可调优的代码,然后调优它以求获得足够的速度 67 | - 短期看,重构可能会让软件变慢,但它的优化阶段的软件性能调优更容易,最终还是会得到好的效果 68 | 69 | ### 2.9 重构起源何处 70 | 71 | - 优秀的程序员肯定会花一些时间来清理自己的代码,因为他们确定自己几乎无法一开始就写出整洁的代码 72 | 73 | ### 2.10 自动化重构 74 | 75 | - 强大的IDE会让重构变得很轻松 76 | 77 | ## 3 代码的坏味道 78 | 79 | - 神秘命名:如果想不出一个好的名字,说明背后很可能隐藏着更深的设计问题 80 | - 重复代码:优化:对比差异,提取相同。 81 | - 过长函数:优化:条件、循环、公共集中的过程提取处理 82 | - 过长参数列表:优化:使用对象合并参数 83 | - 全局数据:优化:合并数据到方法、类成员中 84 | - 可变数据:优化:函数式编程、数据永不改变 85 | - 发散式变化:做出的某个模块的小修改,必须修改某个类的多个函数。优化:每次只关心一个上下文,将联动的改变提取处理 86 | - 霰弹式修改:每次遇到变化,都必须在很多*不同的类*内做出许多小修改。优化:提取公共方法 87 | - 依恋情结:如果一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处的模块内部交流 88 | - 模块化:力求将代码分出区域,最大化区域内部的交互、最小化跨区域的交互。所谓高内聚,低耦合 89 | - 数据泥团:两个类中相同的字段、许多函数签名中相同的参数。优化:提取公共字段为一个类、对象 90 | - 基本类型偏执:一些基本类型无法表示一个数据的真实意义,例如电话号码、温度等。优化:使用类、对象字符串类型变量取代基本类型 91 | - 重复的 `switch`:优化:使用策略模式、提取子类 92 | - 循环语句:同*过长函数* 93 | - 冗赘的元素:优化:内联、删除 94 | - 夸夸其谈通用性:优化:内联、删除 95 | - 临时字段:优化:内联、删除 96 | - 过长的消息链:优化:减少委托关系 97 | - 中间人:优化:用继承替代代理委托 98 | - 内幕交易:优化:合并相同的联系,提取不同的成分 99 | - 过大的类:优化:提取类、子类、接口 100 | - 异曲同工的类:优化:提取公共类、使用子类继承 101 | - 纯数据类:它们拥有一些字段,以及访问、读写这些字段的函数。优化:将相关操作封装进去,降低 `public` 成员变量 102 | - 被拒绝的遗赠:如果子类继承超类的数据和方法,但不使用。优化:用内联数据和方法、代理委托替代继承关系 103 | - 注释:当你感觉需要编写注释时,请先尝试重构,试着让所有注释变得多余 104 | 105 | ## 4 构筑测试体系 106 | 107 | ### 4.1 自测试代码的价值 108 | 109 | - 程序员编写代码的时间仅占所有时间中很少的一部分,但是花费在调试上的时间是最多的。修复bug通常是比较快的,但找出bug所在却是一场噩梦 110 | - 确保所有测试都是完全自动化,让他们检查自己的测试结果 111 | - 一套测试就是一个强大的bug侦探器,能够大大缩减查找bug所需的时间 112 | 113 | ### 4.2 测试代码示例 114 | 115 | ### 4.3 第一个测试 116 | 117 | - 总是确保测试不该通过时,会产生失败 118 | - 频繁地运行测试,对于你正在处理的代码与其对应的测试至少每隔几分钟就要运行一次,每天至少运行一次所有的测试 119 | 120 | ### 4.4 再添加一个测试 121 | - 编写为臻完善的测试并经常运行,好过对完美测试的无尽等待 122 | - 保持每个测试用例独立性,避免产生共享对象。因为测试之间会通过共享产生交互,而测试的结果就会受测试运行次序的影响,导致测试结果的不确定性 123 | - 例子 124 | 125 | ```javascript 126 | describe('province', () => { 127 | const shanghai = new Province('shanghai'); 128 | it('shortfall', () => { 129 | expect(shanghai.shortfall).equal(5); 130 | }) 131 | }) 132 | ``` 133 | ```javascript 134 | describe('province', () => { 135 | let shanghai = null; 136 | beforeEach(() => { 137 | shanghai = new Province('shanghai'); 138 | }) 139 | it('shortfall', () => { 140 | expect(shanghai.shortfall).equal(5) 141 | }) 142 | }) 143 | ``` 144 | 145 | ### 4.5 修改测试夹具 146 | 147 | - 配置 - 检查 - 验证 148 | - 准备 - 行为 - 断言 149 | 150 | ### 4.6 探测边界条件 151 | 152 | - 考虑可能出错的边界条件,把测试火力集中在那儿 153 | - 不要因为测试无法捕捉所有的bug就不写测试,因为测试的确可以捕捉到大多数bug 154 | - 任何测试都不能证明一个程序没有bug 155 | - 当测试数量达到一定程度后,继续增加测试代理的边际效用会递减 156 | - 应该把测试集中在可能出错的地方,观察代码,看哪儿变得复杂、哪些地方可能出错 157 | 158 | ### 4.7 测试远不止如此 159 | 160 | - 一个架构的好坏,很大程度上要取决于它的可测试性,这是一个好的行业趋势 161 | - 每当收到bug报告,请先写一个单元测试来暴露这个bug 162 | - 一个测试集是否够好,最好的衡量标准其实是主观的,试问自己:如果有人在代码里引入了一个缺陷,自己有多大的自信它能被测试集发现 163 | 164 | ## 5 介绍重构名录 165 | 166 | ## 6 第一组重构 167 | 168 | ### 6.1 提炼函数 169 | 170 | - 对立:[内联函数](#62内联函数) 171 | 172 | - 目的:将意图与实现分开。意图 == 主干;实现 == 分支的实现 173 | 174 | - 场景:如果需要花时间浏览一段代码才能弄清它到底干什么,那么就应该将其提炼到一个函数中,并根据它所做的事为其命名。以后再读到这段代码时,可以一眼就能知道函数的用途,大多数根本不需要关心函数如何实现。 175 | 176 | - 例子: 177 | ```javascript 178 | function printOwing(invoice){ 179 | printBanner(); 180 | const outstanding = calculateOutstanding(); 181 | // print details 182 | console.info('name:', invoice.name); 183 | console.info('amount:', outstanding); 184 | } 185 | ``` 186 | ```javascript 187 | function printOwing(invoice){ 188 | printBanner(); 189 | const outstanding = calculateOutstanding(); 190 | printDetails(outstanding, invoice); 191 | function printDetails(outstanding, invoice) { 192 | console.info('name:', invoice.name); 193 | console.info('amount:', outstanding); 194 | } 195 | } 196 | ``` 197 | 198 | ### 6.2 内联函数 199 | 200 | - 对立:[提炼函数](#61-提炼函数) 201 | 202 | - 目的:去除不必要间接层/委托层,降低系统复杂度 203 | 204 | - 场景: 205 | - 一堆不合理的函数,可以将其内联到一个大型函数中,再重新提炼到小函数中 206 | - 代码太多间接层,使系统中的所有函数都似乎只是对另一个函数简单的委托 207 | 208 | - 例子: 209 | ```javascript 210 | function getRating(driver){ 211 | return moreThanFiveDeliveries(driver) ? 2 : 1; 212 | } 213 | 214 | function moreThanFiveDeliveries(driver){ 215 | return driver.deliveries > 5; 216 | } 217 | ``` 218 | ```javascript 219 | function getRating(driver){ 220 | return driver.deliveries > 5 ? 2 : 1; 221 | } 222 | ``` 223 | 224 | ### 6.3 提炼变量 225 | 226 | - 对立:[内联变量](#64-内联变量) 227 | 228 | - 目的:将复杂的表达式使用变量说明 229 | 230 | - 例子: 231 | ```javascript 232 | return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100); 233 | ``` 234 | ```javascript 235 | const basePrice = order.quantity * order.itemPrice; 236 | const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; 237 | const shipping = Math.min(basePrice * 0.1, 100); 238 | return basePrice - quantityDiscount + shipping; 239 | ``` 240 | 241 | ### 6.4 内联变量 242 | 243 | - 对立:[提炼变量](#63-提炼变量) 244 | 245 | - 目的:去除不必要变量 246 | 247 | - 场景:表达式比变量更有表现力 248 | 249 | - 例子: 250 | ```javascript 251 | const basePrice = order.basePrice; 252 | return basePrice > 25; 253 | ``` 254 | ```javascript 255 | return order.basePrice > 25; 256 | ``` 257 | 258 | ### 6.5 改变函数声明 259 | 260 | - 目的:好名字能让人一眼看出函数的用途,而不必看代码实现 261 | 262 | - 使用: 263 | - 先写一句注释描述这个函数的用途,再把这句注释变成函数的名字 264 | 265 | - 例子: 266 | ```javascript 267 | function calc(){} 268 | ``` 269 | ```javascript 270 | function calcOrder(){} 271 | ``` 272 | 273 | ### 6.6 封装变量 274 | 275 | - 目的: 276 | - 重构数据转移为重构函数,更易于处理 277 | - 监控数据的变化 278 | 279 | - 场景:如果数据的可访问范围大 280 | 281 | - 例子: 282 | ```javascript 283 | let defaultOwner = {}; 284 | ``` 285 | ```javascript 286 | let defaultOwner = {}; 287 | export function defaultOwner(){ 288 | return defaultOwner; 289 | } 290 | export function getDefaultOwner(arg){ 291 | defaultOwner = arg; 292 | } 293 | ``` 294 | 295 | ### 6.7 变量改名 296 | 297 | - 目的:好名字可让上下文更清晰 298 | 299 | - 例子: 300 | ```javascript 301 | const a = height * width; 302 | ``` 303 | ```javascript 304 | const area = height * width; 305 | ``` 306 | 307 | ### 6.8 引入参数对象 308 | 309 | - 目的:组织数据结构,让数据项之间的关系更清晰,参数列表也能缩短 310 | 311 | - 场景:一个函数接受多个参数 312 | 313 | - 例子: 314 | ```javascript 315 | function invoice(startDate, endDate){} 316 | function received(starDate, endDate){} 317 | ``` 318 | ```javascript 319 | function invoice(dateRange){} 320 | function received(dateRanges){} 321 | ``` 322 | 323 | ### 6.9 函数组合成类 324 | 325 | - 目的: 326 | - 对象内部调用这些函数可以少传参数,从而简化函数调用,而且一个对象可更方便传递给系统的其他部分 327 | - 客户端修改对象的核心数据,通过计算得出的派生数据会自动与核心数据保存一致 328 | 329 | - 场景:如果一组函数形影不离地操作同一块数据(通常是将这块数据作为参数传递给函数) 330 | 331 | - 例子: 332 | ```javascript 333 | function base(reading){} 334 | function taxableCharge(reading){} 335 | function calcBaseCharge(reading){} 336 | ``` 337 | ```javascript 338 | class Reading{ 339 | base(){} 340 | taxableCharge(){} 341 | calcBaseCharge(){} 342 | } 343 | ``` 344 | 345 | ### 6.10 函数组合成变换 346 | 347 | - 目的: 348 | - 增强数据,将数据逻辑统一在一个地方处理 349 | - 高内聚 350 | 351 | - 场景:需要把数据放到另一个程序中运行,计算出各种派生信息 352 | 353 | - 例子: 354 | ```javascript 355 | function base(reading){} 356 | function taxableCharge(reading){} 357 | ``` 358 | ```javascript 359 | import { cloneDeep } from 'lodash'; 360 | function enrichReading(arg){ 361 | const reading = cloneDeep(arg); 362 | reading.baseCharge = base(reading); 363 | reading.taxableCharge = taxableCharge(reading); 364 | return reading; 365 | } 366 | ``` 367 | 368 | ### 6.11 拆分阶段 369 | 370 | - 目的:保证单一原则,一段代码只做一件事 371 | 372 | - 场景:如果一段代码同时处理两件或者更多不同的事情 373 | 374 | - 例子: 375 | ```javascript 376 | const orderArr = orderStr.split(/\s+/); 377 | const productPrice = priceList(order[0].split('-')[1]); 378 | const orderPrice = parseInt(orderArr[1]) * productPrice; 379 | ``` 380 | ```javascript 381 | const orderRecord = parseOrder(order); 382 | const orderPrice = price(orderRecord, priceList); 383 | 384 | function parseOrder(str){ 385 | const values = str.split(/\s+/); 386 | return { 387 | priceId: values[0].split('-')[1], 388 | quantity: parseInt(values[1]) 389 | }; 390 | } 391 | 392 | function price(order, priceList){ 393 | return order.quantity * priceList[order.productId]; 394 | } 395 | ``` 396 | 397 | ## 7 封装 398 | 399 | ### 7.1 封装记录 400 | 401 | - 目的: 402 | - 对象可以隐藏结构的细节 403 | - 有助于字段改名 404 | 405 | - 场景:对于可变数据 406 | 407 | - 例子: 408 | ```javascript 409 | const organization = {name: 'John', country: 'GB'}; 410 | ``` 411 | 412 | ```javascript 413 | class Organization{ 414 | constructor(data){ 415 | this._name = data.name; 416 | this._country = data.country; 417 | } 418 | get name(){ 419 | return this._name; 420 | } 421 | set name(arg){ 422 | this._name = arg; 423 | } 424 | get country(){ 425 | return this._country; 426 | } 427 | set country(arg){ 428 | this._country = arg; 429 | } 430 | } 431 | ``` 432 | 433 | ### 7.2 封装集合 434 | 435 | - 目的:控制外界对类中集合的访问权,避免集合被直接修改 436 | 437 | - 场景:类中集合为可变数据 438 | 439 | - 例子: 440 | ```javascript 441 | class Person{ 442 | get courses(){ 443 | return this._courses; 444 | } 445 | set courses(list){ 446 | this._course = list; 447 | } 448 | } 449 | ``` 450 | ```javascript 451 | class Person{ 452 | get course(){ 453 | return this._courses.slice(); 454 | } 455 | addCourse(course){} 456 | removeCourse(course){} 457 | } 458 | ``` 459 | 460 | ### 7.3 以对象取代基本类型 461 | 462 | - 目的:扩展或者增强数据的行为 463 | 464 | - 场景:如果数据项需要更多的含义或者行为时 465 | 466 | - 例子: 467 | ```javascript 468 | orders.filter(o => 'high' === o.priority || 'rush' === o.priority); 469 | ``` 470 | ```javascript 471 | orders.filter(o => o.priority.higherThan(new Priority('normal'))); 472 | function Priority(){} 473 | ``` 474 | 475 | ### 7.4 以查询取代临时变量 476 | 477 | - 目的: 478 | - 新函数与原函数之间的边界更清晰 479 | - 便于代码抽离 480 | - 利于函数复用 481 | 482 | - 场景:那些值被计算一次且之后不再被修改的变量 483 | 484 | - 例子: 485 | ```javascript 486 | class Price{ 487 | getPrice(){ 488 | const basePrice = this._quantity * this._itemPrice; 489 | return basePrice > 1000 ? basePrice * 0.95 : basePrice * 0.98; 490 | } 491 | } 492 | ``` 493 | ```javascript 494 | class Price{ 495 | get basePrice(){ 496 | return this._quantity * this._itemPrice; 497 | } 498 | getPrice(){ 499 | return this.basePrice > 1000 ? this.basePrice * 0.95 : this.basePrice * 0.98; 500 | } 501 | } 502 | ``` 503 | 504 | ### 7.5 提取类 505 | 506 | - 对立:[内联类](#76-内联类) 507 | 508 | - 目的:将大类分成小类 509 | 510 | - 场景:维护一个大量函数和数据的类 511 | 512 | - 例子: 513 | ```javascript 514 | class Person{ 515 | get officeAreaCode(){ 516 | return this._officeAreaCode; 517 | } 518 | get officeNumber(){ 519 | return this._officeNumber; 520 | } 521 | } 522 | ``` 523 | ```javascript 524 | class Person{ 525 | constructor(telephoneNumber){ 526 | this._telephoneNumber = telephoneNumber; 527 | } 528 | get officeAreaCode(){ 529 | return this._telephoneNumber.areaCode; 530 | } 531 | get officeNumber(){ 532 | return this._telephoneNumber.number; 533 | } 534 | } 535 | class TelephoneNumber{ 536 | get areaCode(){ 537 | return this._areaCode; 538 | } 539 | get number(){ 540 | return this._number; 541 | } 542 | } 543 | ``` 544 | 545 | ### 7.6 内联类 546 | 547 | - 对立:[提炼类](#75-提炼类) 548 | 549 | - 目的:减少不必要的类 550 | 551 | - 场景: 552 | - 如果一个类不再承担足够的责任 553 | - 重新分类两个类的不同职责 554 | 555 | - 例子: 556 | ```javascript 557 | class Person{ 558 | constructor(telephoneNumber){ 559 | this._telephoneNumber = telephoneNumber; 560 | } 561 | get officeAreaCode(){ 562 | return this._telephoneNumber.areaCode; 563 | } 564 | get officeNumber(){ 565 | return this._telephoneNumber.number; 566 | } 567 | } 568 | class TelephoneNumber{ 569 | get areaCode(){ 570 | return this._areaCode; 571 | } 572 | get number(){ 573 | return this._number; 574 | } 575 | } 576 | ``` 577 | ```javascript 578 | class Person{ 579 | get officeAreaCode(){ 580 | return this._officeAreaCode; 581 | } 582 | get officeNumber(){ 583 | return this._officeNumber; 584 | } 585 | } 586 | ``` 587 | 588 | ### 7.7 隐藏委托关系 589 | 590 | - 对立:[移除中间人](#78-移除中间人) 591 | 592 | - 目的:每个模块尽可能减少了解系统的其他部分,把部分依赖关系隐藏起来,减少调用者双方了解更多细节 593 | 594 | - 场景:如果被调方接口频繁修改时 595 | 596 | - 例子: 597 | ```javascript 598 | class Person{ 599 | constructor(name){ 600 | this._name = name; 601 | } 602 | get name(){ 603 | return this._name; 604 | } 605 | get department(){ 606 | return this._department; 607 | } 608 | set department(name){ 609 | this._department = name; 610 | } 611 | } 612 | class Department{ 613 | construtor(department){ 614 | this._department = department; 615 | } 616 | get manager(){ 617 | if(this._department === 'xx'){ 618 | return 'xx manager'; 619 | } 620 | return 'default manager'; 621 | } 622 | } 623 | const person = new Person('John'); 624 | person.department = new Department('human resource'); 625 | const manager = person.department.manager; 626 | ``` 627 | ```javascript 628 | class Person{ 629 | constructor(name){ 630 | this._name = name; 631 | } 632 | get name(){ 633 | return this._name; 634 | } 635 | get department(){ 636 | return this._department; 637 | } 638 | set department(name){ 639 | this._department = name; 640 | } 641 | get manager(){ 642 | return this._department.manager; 643 | } 644 | } 645 | class Department{ 646 | construtor(department){ 647 | this._department = department; 648 | } 649 | get manager(){ 650 | if(this._department === 'xx'){ 651 | return 'xx manager'; 652 | } 653 | return 'default manager'; 654 | } 655 | } 656 | const person = new Person('John'); 657 | person.department = new Department('human resource'); 658 | const manager = person.manager; 659 | ``` 660 | 661 | ### 7.8 移除中间人 662 | 663 | - 对立:[隐藏委托关系](#77-隐藏委托关系) 664 | 665 | - 目的:减少不不必要的委托 666 | 667 | - 场景:如果过多的转发函数没有让程序本身提升的扩展性,就应删除部分委托 668 | 669 | - 例子:与之相反[隐藏委托关系](#77-隐藏委托关系) 670 | ```javascript 671 | const person = new Person('John'); 672 | person.department = new Department('human resource'); 673 | const manager = person.manager; 674 | ``` 675 | ```javascript 676 | const person = new Person('John'); 677 | person.department = new Department('human resource'); 678 | const manager = person.department.manager; 679 | ``` 680 | 681 | ### 7.9 替换算法 682 | 683 | - 目的:用简单算法处理 684 | 685 | - 场景:随着对业务不断深入,发觉有更简单的算法实现 686 | 687 | - 例子: 688 | ```javascript 689 | function foundPerson(people){ 690 | for(let i = 0; i < people.length; i++){ 691 | if(people[i] === 'John'){ 692 | return 'John'; 693 | } 694 | if(people[i] === 'Maria'){ 695 | return 'Maria'; 696 | } 697 | if(people[i] === 'Mike'){ 698 | return 'Mike'; 699 | } 700 | } 701 | return ''; 702 | } 703 | ``` 704 | ```javascript 705 | function foundPerson(people){ 706 | const candidate = ['John', 'Maria', 'Mike']; 707 | return people.find(p => candidate.includes(p)) || ''; 708 | } 709 | ``` 710 | 711 | ## 8 搬移特性 712 | 713 | ### 8.1 搬移函数 714 | 715 | - 目的:减少对不常用函数的外部依赖,增加常用函数的内部依赖,高内聚 716 | 717 | - 场景:如果一个方法频繁调用别处的一个函数,并且被频繁调用的函数在该上下文关系不大时 718 | 719 | - 例子: 720 | ```javascript 721 | class Account{ 722 | get overdraftCharge(){} 723 | } 724 | ``` 725 | ```javascript 726 | class AccountType{ 727 | get overdraftCharge(){} 728 | } 729 | ``` 730 | 731 | > 将内聚放在一个类中,改名;变量为名词;方法为动词。 732 | 733 | ### 8.2 搬移字段 734 | 735 | - 目的:高内聚,将不属于当前类的属性搬到另一个类中 736 | 737 | - 场景:如果修改一条记录时,总是需要同时改动另一个记录 738 | 739 | - 例子: 740 | ```javascript 741 | class Customer{ 742 | constructor(plan, discountRate){ 743 | this._plan = plan; 744 | this._discountRate = discountRate; 745 | } 746 | get plan(){ 747 | return this._plan; 748 | } 749 | get discountRate(){ 750 | return this._discountRate; 751 | } 752 | } 753 | const john = new Customer('John'); 754 | console.info(john.plan); 755 | console.info(john.discountRate); 756 | ``` 757 | ```javascript 758 | class Customer{ 759 | constructor(plan){ 760 | this._plan = plan; 761 | } 762 | get plan(){ 763 | return this._plan; 764 | } 765 | get discountRate(){ 766 | return this.plan.discountRate; 767 | } 768 | } 769 | const john = new Customer({ 770 | name: 'John', 771 | discountRate: 0.25 772 | }); 773 | console.info(john.plan); 774 | console.info(john.discountRate); 775 | ``` 776 | 777 | ### 8.3 搬移语句到函数 778 | 779 | - 对立:[搬移语句到调用者](#83-搬移语句到调用者) 780 | 781 | - 目的:消除重复,提取公共内容,即表现一致的行为 782 | 783 | - 场景:发现调用某个函数时,总有一些相同的代码也需要每次执行 784 | 785 | - 例子: 786 | ```javascript 787 | function getResult(person) { 788 | let result = []; 789 | result.push(`
title: ${person.photo.title}
`); 790 | result = result.concat(photoData(person.photo)); 791 | function photoData(photo){ 792 | return [ 793 | `location: ${photo.location}
`, 794 | `date: ${photo.date}
` 795 | ]; 796 | } 797 | return result; 798 | }; 799 | getResult({photo: {title: 'title', location: 'New York', data: '01/16/2022'}}); 800 | ``` 801 | ```javascript 802 | function getResult(person) { 803 | function photoData(photo){ 804 | return [ 805 | `title: ${photo.title}
`, 806 | `location: ${photo.location}
`, 807 | `date: ${photo.date}
` 808 | ]; 809 | } 810 | return [].concat(photoData(person.photo)); 811 | }; 812 | getResult({photo: {title: 'title', location: 'New York', data: '01/16/2022'}}); 813 | ``` 814 | 815 | ### 8.4 搬移语句到调用者 816 | 817 | - 对立:[搬移语句到函数](#83-搬移语句到函数) 818 | 819 | - 目的:将可变的行为搬移到调用者内部 820 | 821 | - 场景:以往多个地方公共的行为,如今需要在某些调用点表现不同的行为,并且调用点与调用者之间的边界差别不大。如果差别较大的,只能重新设计 822 | 823 | - 例子: 824 | ```javascript 825 | function demoWrapper(outStream, person){ 826 | emitPhotoData(outStream, person.photo); 827 | function emitPhotoData(outStream, photo){ 828 | outStream.write(`title: ${photo.title}
`); 829 | outStream.write(`title: ${photo.location}
`); 830 | } 831 | } 832 | 833 | const person = {photo: {title: 'title', location: 'New York'}}; 834 | demoWrapper(document, person); 835 | ``` 836 | 837 | ```javascript 838 | function demoWrapper(outStream, person){ 839 | emitPhotoData(outStream, person.photo); 840 | outStream.write(`title: ${person.photo.title}
`); 841 | function emitPhotoData(outStream, photo){ 842 | outStream.write(`title: ${photo.location}
`); 843 | } 844 | } 845 | 846 | const person = {photo: {title: 'title', location: 'New York'}}; 847 | demoWrapper(document, person); 848 | ``` 849 | 850 | ### 8.5 以函数调用取代内联代码 851 | 852 | - 目的:消除重复 853 | 854 | - 场景:如果一些内联代码做的事情是已有函数可以做到的 855 | 856 | - 例子: 857 | ```javascript 858 | function demoWrapper(states){ 859 | let hasMa = false; 860 | for(const i of states){ 861 | if(i === 'MA'){ 862 | hasMa = true; 863 | } 864 | } 865 | return hasMa; 866 | } 867 | demoWrapper(['MA','AL']); 868 | ``` 869 | 870 | ```javascript 871 | function demoWrapper(states){ 872 | return states.includes('MA'); 873 | } 874 | demoWrapper(['MA','AL']); 875 | ``` 876 | 877 | ### 8.6 移动语句 878 | 879 | - 目的:高内聚内部代码 880 | 881 | - 场景:如果有几行代码取用了同一个数据结构,那么最好让它们在一起 882 | 883 | - 例子: 884 | ```javascript 885 | const retrievePricingPlan = () => ({unit: () => 0.25}); 886 | const retrieveOrder = () => ({name: 'abc'}); 887 | const pricePlan = retrievePricingPlan(); 888 | const order = retrieveOrder(); 889 | let charge; 890 | const chargePerUnit = pricePlan.unit(); 891 | ``` 892 | ```javascript 893 | const retrievePricingPlan = () => ({unit: () => 0.25}); 894 | const pricePlan = retrievePricingPlan(); 895 | const chargePerUnit = pricePlan.unit(); 896 | const retrieveOrder = () => ({name: 'abc'}); 897 | const order = retrieveOrder(); 898 | let charge; 899 | ``` 900 | 901 | ### 8.7 拆分循环 902 | 903 | - 目的:保持循环内部只做一件事 904 | 905 | - 场景:如果循环内部身兼多职 906 | 907 | - 例子: 908 | ```javascript 909 | const people = [{age: 20, salary: 20000}, {age: 22, salary: 30000}]; 910 | let totalAge = 0; 911 | let totalSalary = 0; 912 | for(const p of people){ 913 | totalAge += p.age; 914 | totalSalary += p.salary; 915 | } 916 | const averageAge = totalAge / people.length; 917 | ``` 918 | ```javascript 919 | const people = [{age: 20, salary: 20000}, {age: 22, salary: 30000}]; 920 | let totalAge = 0; 921 | for(const p of people){ 922 | totalAge += p.age; 923 | } 924 | let totalSalary = 0; 925 | for(const p of people){ 926 | totalSalary += p.salary; 927 | } 928 | const averageAge = totalAge / people.length; 929 | ``` 930 | 931 | > 先进行重构,在进行性能优化。将代码变得清晰,对后期的扩展、优化,都极其方便 932 | 933 | ### 8.8 以管道取代循环 934 | 935 | - 目的:提高代码可读性 936 | 937 | - 场景:如果代码的逻辑可以通过内置方法处理 938 | 939 | - 例子: 940 | ```javascript 941 | const names = []; 942 | const values = [{job: 'programmer', name: 'John'}, {job: 'financer', name: 'Kaly'}]; 943 | for(const i of values){ 944 | if(i.job === 'programmer'){ 945 | names.push(i.name); 946 | } 947 | } 948 | console.info(names); 949 | ``` 950 | ```javascript 951 | const values = [{job: 'programmer', name: 'John'}, {job: 'financer', name: 'Kaly'}]; 952 | const names = values 953 | .filter(i => i.job === 'programmer') 954 | .map(i => i.name); 955 | console.info(names); 956 | ``` 957 | 958 | ### 8.9 移除死代码 959 | 960 | - 目的:移除无用代码 961 | 962 | ## 9 重新组织数据 963 | 964 | ### 9.1 拆分代码 965 | 966 | - 目的:每个变量只承担一个责任,同一个变量承担两件不同的事情,会令代码阅读者糊涂 967 | 968 | - 场景:大多数情况下变量只赋值一次,除了:循环变量(例如:`for(let i =0; i < 5; i++)` 中的`i`),收集结果变量 969 | 970 | - 例子: 971 | ```javascript 972 | let height = 10; 973 | let width = 20; 974 | let temp = 2 * (height + width); 975 | temp = height * width; 976 | ``` 977 | ```javascript 978 | const height = 10; 979 | const width = 20; 980 | const perimeter = 2 * (height * width); 981 | const area = height * width; 982 | ``` 983 | 984 | > 变量声明可以刚开始声明为 `const`,如果发觉需要重复赋值,再改为 `let` 985 | 986 | ### 9.2 字段改名 987 | 988 | - 目的:好的名字可以帮助阅读者更易理解 989 | 990 | - 例子: 991 | ```javascript 992 | class Organization{ 993 | get name(){} 994 | } 995 | ``` 996 | ```javascript 997 | class Organization{ 998 | get title(){} 999 | } 1000 | ``` 1001 | 1002 | ### 9.3 以查询取代派生变量 1003 | 1004 | - 目的:减少方法中的*副作用*,单一原则 1005 | 1006 | - 场景:对数据的修改常常导致代码的各个部分以丑陋的形式互相耦合:在一处修改数据,却在另一处造成难以发现的破坏 1007 | 1008 | - 例子: 1009 | ```javascript 1010 | class ProductPlan{ 1011 | constructor(num){ 1012 | this._product = num; 1013 | } 1014 | get product(){ 1015 | return this._product; 1016 | } 1017 | applyAdjustment(num){ 1018 | this._accumulator.push(num); 1019 | this._product += num; 1020 | } 1021 | showProductList(){ 1022 | console.info(this._accumulator); 1023 | } 1024 | } 1025 | ``` 1026 | ```javascript 1027 | class Product{ 1028 | constructor(num){ 1029 | this._initialProduct = num; 1030 | this._accumulator = []; 1031 | } 1032 | get productTotal(){ 1033 | return this._initialProduct + this.productCalculator; 1034 | } 1035 | get productCalculator(){ 1036 | return this._accumulator.reduce((sum, cur) => sum + cur, 0); 1037 | } 1038 | applyAdjustment(num){ 1039 | this._accumulator.push(num); 1040 | } 1041 | showProductList(){ 1042 | console.info(this._accumulator); 1043 | } 1044 | } 1045 | ``` 1046 | 1047 | ### 9.4 将引用对象改为值对象 1048 | 1049 | - 对立:[将值对象改为引用对象](#94-将值对象改为引用对象) 1050 | 1051 | - 目的:值对象的不可变性处理起来更容易,可以任意的传递,防止被外部修改 1052 | 1053 | - 场景:如果不需要改变值的引用关系,每个值是不可变的 1054 | 1055 | - 例子: 1056 | ```javascript 1057 | class Person{ 1058 | constructor(){ 1059 | this._telephoneNum = new TelephoneNumber(); 1060 | } 1061 | get officeAreaCode(){ 1062 | return this._telephoneNum.areaCode; 1063 | } 1064 | set officeAreaCode(code){ 1065 | this._telephoneNum.areaCode = code; 1066 | } 1067 | get officeNumber(){ 1068 | return this._telephoneNum.number; 1069 | } 1070 | set officeNumber(number){ 1071 | this._telephoneNum.number = number; 1072 | } 1073 | } 1074 | class TelephoneNumber{ 1075 | get areaCode(){ 1076 | return this._areaCode; 1077 | } 1078 | set areaCode(code){ 1079 | this._areaCode = code; 1080 | } 1081 | get number(){ 1082 | return this._number; 1083 | } 1084 | set number(number){ 1085 | this._number = number; 1086 | } 1087 | } 1088 | ``` 1089 | ```javascript 1090 | class Person{ 1091 | get officeAreaCode(){ 1092 | return this._telephoneNum.areaCode; 1093 | } 1094 | set officeAreaCode(code){ 1095 | this._telephoneNum = new TelephoneNumber(code, this.officeNumber); 1096 | } 1097 | get officeNumber(){ 1098 | return this._telephoneNum.number; 1099 | } 1100 | set officeNumber(number){ 1101 | this._telephoneNum = new TelephoneNumber(this.officeAreaCode, number); 1102 | } 1103 | } 1104 | class TelephoneNumber{ 1105 | constructor(areCode, number){ 1106 | this._areaCode = areaCode; 1107 | this._number = number; 1108 | } 1109 | get areaCode(){ 1110 | return this._areaCode; 1111 | } 1112 | set areaCode(code){ 1113 | this._areaCode = code; 1114 | } 1115 | get number(){ 1116 | return this._number; 1117 | } 1118 | set number(number){ 1119 | this._number = number; 1120 | } 1121 | } 1122 | ``` 1123 | 1124 | ### 9.5 将值对象改为引用对象 1125 | 1126 | - 目的:保持数据共享 1127 | 1128 | - 场景:如果数据结构中包含多个记录,而这些记录都有关联到同一个逻辑的数据结构,例如一个数据的改动,需要共享到整个数据集 1129 | 1130 | - 例子: 1131 | ```javascript 1132 | class Customer{ 1133 | constructor(id){ 1134 | this._id = id; 1135 | } 1136 | get id(){ 1137 | return this._id; 1138 | } 1139 | } 1140 | class Order{ 1141 | constructor(data){ 1142 | this._number = data.number; 1143 | this._customer = new Customer(data.customer); 1144 | } 1145 | get customer(){ 1146 | return this._customer; 1147 | } 1148 | } 1149 | const order = new Order({ 1150 | number: 100, 1151 | customer: '001' 1152 | }); 1153 | ``` 1154 | 1155 | ```javascript 1156 | class Customer{ 1157 | constructor(id){ 1158 | this._id = id; 1159 | } 1160 | get id(){ 1161 | return this._id; 1162 | } 1163 | } 1164 | class Order{ 1165 | constructor(data){ 1166 | this._number = data.number; 1167 | this._customer = repo.registerCustomer(data.customer); 1168 | } 1169 | get customer(){ 1170 | return this._customer; 1171 | } 1172 | } 1173 | const repo = { 1174 | repoData: { 1175 | customers: new Map() 1176 | }, 1177 | registerCustomer(id){ 1178 | if(!this.repoData.customers.has(id)){ 1179 | this.repoData.customers.set(id, new Customer(id)); 1180 | } 1181 | return this.repoData.customers.get(id); 1182 | } 1183 | } 1184 | new Order({ 1185 | number: 100, 1186 | customer: '001' 1187 | }); 1188 | console.info(repo.repoData.customers.get('001')); 1189 | new Order({ 1190 | number: 100, 1191 | customer: '002' 1192 | }); 1193 | console.info(repo.repoData.customers.get('002')); 1194 | ``` 1195 | 1196 | ## 10 简化条件逻辑 1197 | 1198 | ### 10.1 分解条件表达式 1199 | 1200 | - 目的:简化条件,易于理解代码逻辑 1201 | 1202 | - 场景:当检查处理逻辑复杂时 1203 | 1204 | - 例子: 1205 | ```javascript 1206 | function demoWrapper(date, plan, quantity){ 1207 | let charge; 1208 | if(!date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd)){ 1209 | charge = quantity * plan.summerRate; 1210 | }else{ 1211 | charge = quantity * plan.regularRate + plan.regularServiceCharge; 1212 | } 1213 | return charge; 1214 | } 1215 | ``` 1216 | ```javascript 1217 | function demoWrapper(date, plan, quantity){ 1218 | function isSummer(){ 1219 | return !date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd); 1220 | } 1221 | function summerCharge(){ 1222 | return quantity * plan.summerRate; 1223 | } 1224 | function regularCharge(){ 1225 | return quantity * plan.regularRate + plan.regularServiceCharge; 1226 | } 1227 | return isSummer() ? summerCharge() : regularCharge(); 1228 | } 1229 | ``` 1230 | 1231 | ### 10.2 合并条件表达式 1232 | 1233 | - 目的:统一处理条件语句 1234 | 1235 | - 场景:当检查条件各不相同,最终行为一致时 1236 | 1237 | - 例子: 1238 | ```javascript 1239 | function demoWrapper(age, experience, isPassTest){ 1240 | if(age < 18) return 'younger'; 1241 | if(experience < 5) return 'younger'; 1242 | if(!isPassTest) return 'younger'; 1243 | } 1244 | ``` 1245 | ```javascript 1246 | function demoWrapper(age, experience, isPassTest){ 1247 | if(isYounger(age, experience, isPassTest)) return 'younger'; 1248 | function isYounger(age, experience, isPassTest){ 1249 | return age<18 || experience < 5 || !isPassTest; 1250 | } 1251 | } 1252 | ``` 1253 | 1254 | ### 10.3 卫语句取代嵌套条件表达式 1255 | 1256 | - 目的:减少逻辑的复杂度 1257 | 1258 | - 场景:当出现需要单独检查某个特定条件时 1259 | 1260 | - 例子: 1261 | ```javascript 1262 | function getPayment(isRead, isSeparated, isRetired){ 1263 | let result; 1264 | if(isRead){ 1265 | result = deadAmount(); 1266 | }else{ 1267 | if(isSeparated){ 1268 | result = separatedAmount(); 1269 | }else{ 1270 | if(isRetired){ 1271 | result = retiredAmount(); 1272 | }else{ 1273 | result = normalAmount(); 1274 | } 1275 | } 1276 | } 1277 | } 1278 | function deadAmount() { 1279 | return 'deadAmount'; 1280 | } 1281 | function separatedAmount() { 1282 | return 'separatedAmount'; 1283 | } 1284 | function retiredAmount() { 1285 | return 'retiredAmount'; 1286 | } 1287 | function normalAmount() { 1288 | return 'normalAmount'; 1289 | } 1290 | ``` 1291 | ```javascript 1292 | function getPayment(isRead, isSeparated, isRetired){ 1293 | if(isRead) return deadAmount(); 1294 | if(isSeparated) return separatedAmount(); 1295 | if(isRetired) return retiredAmount(); 1296 | return normalAmount(); 1297 | } 1298 | function deadAmount() { 1299 | return 'deadAmount'; 1300 | } 1301 | function separatedAmount() { 1302 | return 'separatedAmount'; 1303 | } 1304 | function retiredAmount() { 1305 | return 'retiredAmount'; 1306 | } 1307 | function normalAmount() { 1308 | return 'normalAmount'; 1309 | } 1310 | ``` 1311 | 1312 | ### 10.4 以多态取代条件表达式 1313 | 1314 | - 目的:增强扩展性,减少逻辑的复杂度 1315 | 1316 | - 场景:当多个逻辑处理情况 1317 | 1318 | - 例子: 1319 | ```javascript 1320 | function getBird(){ 1321 | return { 1322 | type: 'EuropeanSwallow' 1323 | } 1324 | } 1325 | const bird = getBird(); 1326 | switch(bird.type){ 1327 | case 'EuropeanSwallow': 1328 | return 'EuropeanSwallow'; 1329 | case 'AfricanSwallow': 1330 | return 'AfricanSwallow'; 1331 | default: 1332 | return 'unknown'; 1333 | } 1334 | ``` 1335 | ```javascript 1336 | class EuropeanSwallow{ 1337 | get name(){ 1338 | return 'EuropeanSwallow'; 1339 | } 1340 | } 1341 | class AfricanSwallow{ 1342 | get name(){ 1343 | return 'AfricanSwallow'; 1344 | } 1345 | } 1346 | ``` 1347 | 1348 | ### 10.5 引入特例 1349 | 1350 | - 目的:提供复用性以及统一性 1351 | 1352 | - 场景:如果某部分逻辑都在检查某个特殊值,并且处理的逻辑也都相同 1353 | 1354 | - 例子: 1355 | ```javascript 1356 | let customer = getCustomer(); 1357 | if(customer === 'unknown'){ 1358 | customerName = 'occupant'; 1359 | } 1360 | function getCustomer(){ return 'normal'; } 1361 | ``` 1362 | ```javascript 1363 | class UnknownCustomer{ 1364 | get name(){ 1365 | return 'occupant'; 1366 | } 1367 | } 1368 | ``` 1369 | 1370 | ### 10.6 引入断言 1371 | 1372 | - 目的:保障传入值是可预测的,预习发现测试的BUG 1373 | 1374 | - 例子: 1375 | ```javascript 1376 | const discountRate = getDiscountRate(); 1377 | let base = 10; 1378 | if(discountRate){ 1379 | base = base - discountRate * base; 1380 | } 1381 | function getDiscountRate(){ return 0.25;} 1382 | ``` 1383 | ```javascript 1384 | let base = 10; 1385 | const discountRate = getDiscountRate(); 1386 | asset(discountRate > 0); 1387 | if(discountRate){ 1388 | base = base - discountRate * base; 1389 | } 1390 | function getDiscountRate(){ return 0.25;} 1391 | ``` 1392 | 1393 | ## 11 重构API 1394 | 1395 | ### 11.1 查询函数和修改函数分离 1396 | 1397 | - 目的:减少函数副作用 == 任何有返回值的函数,都要减少它的副作用 1398 | 1399 | - 场景:当函数中又有查询,又有命令 1400 | 1401 | - 例子: 1402 | ```javascript 1403 | function getTotalOutStandingAndSendBill(customer){ 1404 | const result = customer.invoice.reduce((total, cur)=> cur.amount + total, 0); 1405 | sendBill(result); 1406 | return result; 1407 | } 1408 | function sendBill(){} 1409 | ``` 1410 | ```javascript 1411 | function getTotalStanding(customer){ 1412 | return customer.invoice.reduce((total, cur)=> cur.amount + total, 0); 1413 | } 1414 | function sendBill(){} 1415 | function handler(){ 1416 | const totalOutstanding = getTotalStanding(); 1417 | sendBill(totalOutstanding); 1418 | } 1419 | ``` 1420 | 1421 | ### 11.2 函数参数化 1422 | 1423 | - 目的:增强函数的功能 1424 | 1425 | - 场景:如果发现多个函数逻辑相似,只有某1-2个字面量不同 1426 | 1427 | - 例子: 1428 | ```javascript 1429 | function tenPercentRaise(person){ 1430 | person.salary = person.salary.multiply(1.1); 1431 | } 1432 | function fivePercentRaise(person){ 1433 | person.salary = person.salary.multiply(0.05); 1434 | } 1435 | ``` 1436 | ```javascript 1437 | function raise(person, factor){ 1438 | person.salary = person.salary.multiply(factor); 1439 | } 1440 | ``` 1441 | 1442 | ### 11.3 移除标记参数 1443 | 1444 | - 目的:代码更清晰,减少函数的复杂度。去除flag - 一般为true/false 1445 | 1446 | - 场景:如果参数值影响函数内部的控制流 1447 | 1448 | - 例子: 1449 | ```javascript 1450 | function takeOrder(order){ 1451 | const date = getOrderDate(order, true); 1452 | } 1453 | function takeAnotherOrder(order){ 1454 | const date = getOrderDate(order, false); 1455 | } 1456 | function getOrderDate(order, isExpire){ 1457 | return isExpire ? '02/01/2022' : '02/01/2023'; 1458 | } 1459 | ``` 1460 | ```javascript 1461 | function takeOrder(order){ 1462 | const date = getExpireOrder(order); 1463 | } 1464 | function takeAnotherOrder(order){ 1465 | const date = getNormalOrder(order); 1466 | } 1467 | function getExpireOrder(order){ 1468 | return order.date + 5; 1469 | } 1470 | function getNormalOrder(order){ 1471 | return order.date; 1472 | } 1473 | ``` 1474 | 1475 | ### 11.4 保持对象完整性 1476 | 1477 | - 目的:缩短参数列表,参数配置灵活 1478 | 1479 | - 场景:如果传入多个参数传值 1480 | 1481 | - 例子: 1482 | ```javascript 1483 | const temperature = { low: 10, high: 30 }; 1484 | const { low, high } = temperature; 1485 | if(isValidTemperature(low, high)){ 1486 | console.info('valid'); 1487 | }; 1488 | function isValidTemperature(low, high){ 1489 | return low > 0 && high < 100 && high > low; 1490 | } 1491 | ``` 1492 | ```javascript 1493 | const temperature = { low: 10, high: 30 }; 1494 | if(isValidTemperature(temperature)){ 1495 | console.info('valid'); 1496 | }; 1497 | function isValidTemperature({ low, high }){ 1498 | return low > 0 && high < 100 && high > low; 1499 | } 1500 | ``` 1501 | 1502 | ### 11.5 以查询取代参数 1503 | 1504 | - 对立:[以参数取代查询](#116-以参数取代查询) 1505 | 1506 | - 目的:减少传参,从而减少调用者的成本 1507 | 1508 | - 场景:如果传入多个参数,并且从一个参数推导出另一个参数 1509 | 1510 | - 例子: 1511 | ```javascript 1512 | const employee = {grade: 'A', name: 'John'}; 1513 | availableVacation(employee, employee.grade); 1514 | function availableVacation(employee, grade){ 1515 | console.info(grade); 1516 | } 1517 | ``` 1518 | ```javascript 1519 | const employee = {grade: 'A', name: 'John'}; 1520 | availableVacation(employee); 1521 | function availableVacation(employee){ 1522 | const { grade } = employee; 1523 | console.info(grade); 1524 | } 1525 | ``` 1526 | 1527 | ### 11.6 以参数取代查询 1528 | 1529 | - 对立:[以查询取代参数](#115-以查询取代参数) 1530 | 1531 | - 目的:减少函数的副作用,以及引用关系,保持函数的纯净度 1532 | 1533 | - 场景:如果函数引用了一个全局变量,或者引用想移除的元素 1534 | 1535 | - 例子: 1536 | ```javascript 1537 | const weather = {curTemperature: 20, unit: 'fahrenheit'}; 1538 | const plan = {desc: 'go swimming'}; 1539 | targetTemperature(plan); 1540 | getWeatherUnit(); 1541 | function targetTemperature(plan){ 1542 | const { curTemperature } = weather; 1543 | console.info(plan.desc); 1544 | } 1545 | 1546 | function getWeatherUnit(){ 1547 | return weather.unit; 1548 | } 1549 | ``` 1550 | ```javascript 1551 | const weather = {curTemperature: 20, unit: 'fahrenheit'}; 1552 | const plan = {desc: 'go swimming'}; 1553 | targetTemperature(plan, weather); 1554 | getWeatherUnit(weather); 1555 | 1556 | function targetTemperature(plan, weather){ 1557 | const { curTemperature } = weather; 1558 | console.info(plan.desc); 1559 | } 1560 | 1561 | function getWeatherUnit(weather){ 1562 | return weather.unit; 1563 | } 1564 | ``` 1565 | 1566 | ### 11.7 移除设置函数 1567 | 1568 | - 目的:防止某字段被修改 1569 | 1570 | - 场景:当不希望某个字段被修改时 1571 | 1572 | - 例子: 1573 | ```javascript 1574 | class Person{ 1575 | get id(){} 1576 | set id(name){} 1577 | } 1578 | ``` 1579 | ```javascript 1580 | class Person{ 1581 | get id(){} 1582 | } 1583 | ``` 1584 | 1585 | ### 11.8 以工厂函数取代构造函数 1586 | 1587 | - 目的:增加灵活性 1588 | 1589 | - 场景:不存在继承关系时 1590 | 1591 | - 例子: 1592 | ```javascript 1593 | const leadEngineer = new Employee('name','E'); 1594 | class Employee{} 1595 | ``` 1596 | ```javascript 1597 | const leadEngineer = createEngineer('name'); 1598 | function createEngineer(){} 1599 | ``` 1600 | 1601 | ### 11.9 以命令取代函数 1602 | 1603 | - 对立:[以函数取代命令](#1110-以函数取代命令) 1604 | 1605 | - 目的:命令对象提供更大的灵活性,并且还可以支持撤销、生命周期的管理等附加操作 1606 | 1607 | - 场景:当普通函数无法提供强有力灵活性 1608 | 1609 | - 例子: 1610 | ```javascript 1611 | function score(candidate, medicalExam){ 1612 | const whiteList = ['Haven', 'Kaly']; 1613 | let result = 0; 1614 | if(medicalExam.isPass){ 1615 | result += 10; 1616 | }else{ 1617 | result -= 20; 1618 | } 1619 | if(whiteList.includes(candidate.name)){ 1620 | result += 20; 1621 | } 1622 | return result; 1623 | } 1624 | ``` 1625 | ```javascript 1626 | class Scorer{ 1627 | constructor(candidate, medicalExam){ 1628 | this._candidate = candidate; 1629 | this._medicalExam = medicalExam; 1630 | } 1631 | execute(){ 1632 | const whiteList = ['Haven', 'Kaly']; 1633 | let result = 0; 1634 | if(this._medicalExam.isPass){ 1635 | result += 10; 1636 | }else{ 1637 | result -= 20; 1638 | } 1639 | if(whiteList.includes(this._candidate.name)){ 1640 | result += 20; 1641 | } 1642 | return result; 1643 | } 1644 | } 1645 | ``` 1646 | 1647 | ### 11.10 以函数取代命令 1648 | 1649 | - 对立:[以命令取代函数](#119-以命令取代函数) 1650 | 1651 | - 目的:函数简单化 1652 | 1653 | - 场景:大多数情况下,只想调用一个函数,完成自己的工作,不需要函数那么复杂 1654 | 1655 | - 例子: 1656 | ```javascript 1657 | class ChargeCalculator{ 1658 | constructor(customer, usage){ 1659 | this._customer = customer; 1660 | this._usage = usage; 1661 | } 1662 | execute(){ 1663 | return this._customer.rate * this._usage; 1664 | } 1665 | } 1666 | ``` 1667 | ```javascript 1668 | function charge(customer, usage){ 1669 | return customer.rate * usage; 1670 | } 1671 | ``` 1672 | 1673 | ## 12 处理继承关系 1674 | 1675 | 继承体系里上下调整:[函数上移](#121-函数上移)、[字段上移](#122-字段上移)、[构造函数本体上移](#123-构造函数本体上移)、[函数下移](#124-函数下移)、[字段下移](#125-字段下移) 1676 |