💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
反向重构:搬移语句到调用者(217) ![](https://box.kancloud.cn/8cad5703a273fd6ad71ff4598cd7ce90_463x380.jpeg) ``` result.push(`<p>title: ${person.photo.title}</p>`); result.concat(photoData(person.photo)); function photoData(aPhoto) {  return [   `<p>location: ${aPhoto.location}</p>`,   `<p>date: ${aPhoto.date.toDateString()}</p>`, ]; } ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` result.concat(photoData(person.photo)); function photoData(aPhoto) {  return [   `<p>title: ${aPhoto.title}</p>`,   `<p>location: ${aPhoto.location}</p>`,   `<p>date: ${aPhoto.date.toDateString()}</p>`,  ]; } ``` ### 动机 要维护代码库的健康发展,需要遵守几条黄金守则,其中最重要的一条当属“消除重复”。如果我发现调用某个函数时,总有一些相同的代码也需要每次执行,那么我会考虑将此段代码合并到函数里头。这样,日后对这段代码的修改只需改一处地方,还能对所有调用者同时生效。如果将来代码对不同的调用者需有不同的行为,那时再通过搬移语句到调用者(217)将它(或其一部分)搬移出来也十分简单。 如果某些语句与一个函数放在一起更像一个整体,并且更有助于理解,那我就会毫不犹豫地将语句搬移到函数里去。如果它们与函数不像一个整体,但仍应与函数一起执行,那我可以用提炼函数(106)将语句和函数一并提炼出去。这基本就是我下面要描述的做法了,只是下面还多了内联和改名的步骤。这些清理工作通常有其必要性,可以在完成核心步骤后再择机完成。 ### 做法 - 如果重复的代码段离调用目标函数的地方还有些距离,则先用移动语句(223)将这些语句挪动到紧邻目标函数的位置。 - 如果目标函数仅被唯一一个源函数调用,那么只需将源函数中的重复代码段剪切并粘贴到目标函数中即可,然后运行测试。本做法的后续步骤至此可以忽略。 - 如果函数不止一个调用点,那么先选择其中一个调用点应用提炼函数(106),将待搬移的语句与目标函数一起提炼成一个新函数。给新函数取个临时的名字,只要易于搜索即可。 - 调整函数的其他调用点,令它们调用新提炼的函数。每次调整之后运行测试。 - 完成所有引用点的替换后,应用内联函数(115)将目标函数内联到新函数里,并移除原目标函数。 - 对新函数应用函数改名(124),将其改名为原目标函数的名字。 > 如果你能想到更好的名字,那就用更好的那个。 ### 范例 我将用一个例子来讲解这项手法。以下代码会生成一些关于相片(photo)的HTML: ``` function renderPerson(outStream, person) {  const result = [];  result.push(`<p>${person.name}</p>`);  result.push(renderPhoto(person.photo));  result.push(`<p>title: ${person.photo.title}</p>`);  result.push(emitPhotoData(person.photo));  return result.join("\n"); } function photoDiv(p) {  return [   "<div>",   `<p>title: ${p.title}</p>`,   emitPhotoData(p),   "</div>",  ].join("\n"); } function emitPhotoData(aPhoto) {  const result = [];  result.push(`<p>location: ${aPhoto.location}</p>`);  result.push(`<p>date: ${aPhoto.date.toDateString()}</p>`);  return result.join("\n"); } ``` 这个例子中的`emitPhotoData`函数有两个调用点,每个调用点的前面都有一行类似的重复代码,用于打印与标题(title)相关的信息。我希望能消除重复,把打印标题的那行代码搬移到`emitPhotoData`函数里去。如果`emitPhotoData`只有一个调用点,那我大可直接把代码复制并粘贴过去就完事,但若调用点不止一个,那我就更倾向于用更安全的手法小步前进。 我先选择其中一个调用点,对其应用提炼函数(106)。除了我想搬移的语句,我还把`emitPhotoData`函数也一起提炼到新函数中。 ``` function photoDiv(p) {  return [   "<div>",   zznew(p),   "</div>",  ].join("\n"); } function zznew(p) {  return [   `<p>title: ${p.title}</p>`,   emitPhotoData(p),  ].join("\n"); } ``` 完成提炼后,我会逐一查看`emitPhotoData`的其他调用点,找到该函数与其前面的重复语句,一并换成对新函数的调用。 ``` function renderPerson(outStream, person) {  const result = [];  result.push(`<p>${person.name}</p>`);  result.push(renderPhoto(person.photo));  result.push(zznew(person.photo));  return result.join("\n"); } ``` 替换完`emitPhotoData`函数的所有调用点后,我紧接着应用内联函数(115)将`emitPhotoData`函数内联到新函数中。 ``` function zznew(p) {  return [   `<p>title: ${p.title}</p>`,   `<p>location: ${p.location}</p>`,   `<p>date: ${p.date.toDateString()}</p>`,  ].join("\n"); } ``` 最后,再对新提炼的函数应用函数改名(124),就大功告成了。 ``` function renderPerson(outStream, person) {  const result = [];  result.push(`<p>${person.name}</p>`);  result.push(renderPhoto(person.photo));  result.push(emitPhotoData(person.photo));  return result.join("\n"); } function photoDiv(aPhoto) {  return [   "<div>",   emitPhotoData(aPhoto),   "</div>",  ].join("\n"); } function emitPhotoData(aPhoto) {  return [   `<p>title: ${aPhoto.title}</p>`,   `<p>location: ${aPhoto.location}</p>`,   `<p>date: ${aPhoto.date.toDateString()}</p>`,  ].join("\n"); } ``` 同时我会记得调整函数参数的命名,使之与我的编程风格保持一致。