大重写

重写整个代码库是对开发人员的一种诱惑。是的,阅读现有代码往往比编写新代码更难;但请记住,重写需要时间,而且可能会给企业带来巨大损失。

请始终牢记,任何一个项目的技术债务总和都不可能超过从零开始的项目。

Maiz Lulkin 在一篇精彩的博文中写道:

大改写的问题在于,它们是解决文化问题的技术解决方案。

大规模重写的效率低得可怕,尤其是当你根本无法保证开发人员现在会知道更多的时候。架构新系统并在最后期限内迁移数据是一项艰巨的任务。

除此之外,部署大型重写工作也会带来巨大的问题;在应用程序的整个代码库中部署这样的变更可能是致命的。尽量以频繁的间隔定期部署代码。尽量一次只改动一件事。

您现有的软件就是您现有的规范。如果进行重写,就等于在遗留代码的基础上编写代码。

幸运的是,还有另一种选择:循环快速改进现有代码库。您可以采取三个主要步骤来改进您的代码库:

  • 测试(单元测试、行为测试等)

  • 服务拆分

  • 完美的分阶段迁移

本书有一章专门讨论重构以及如何改变遗留代码的设计。

自动化测试

您需要测试;是的,自动化测试的编写速度可能会很慢,但它们对于确保重写或重构时不会出错至关重要。

此外,在尽可能接近生产环境的环境中进行测试和开发也是至关重要的。网络服务器软件或数据库权限的微小变化都可能带来灾难性后果。

使用 Vagrant 和 Puppet 或 Docker 等自动部署系统是一个很好的解决方案。

使用 PHPUnit 和 Composer 进行单元测试时,只需将其包含在 composer.json 文件中即可:

{
    "autoload": {
        "psr-4": {
            "IcyApril\\Example": "src/"
        }
    },
    "require": {
        "illuminate/database": "*",
        "phpunit/phpunit": "*",
        "robmorgan/phinx": "*"
    }
}

除此之外,phpunit.xml 文件也很有用,它不仅能让 PHPUnit 知道测试在哪里,还能让 Composer 自动加载器知道它在哪里(这样它就能继续调入类):

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="./vendor/autoload.php">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>

然后,您可以像通常在 PHPUnit 中一样编写测试,例如:

<?php

class App extends PHPUnit_Framework_TestCase
{
    public function testApp()
    {
        $this->assertTrue(true);
    }
}

当然,您还可以根据需要在自动加载器中调入 PHP 类。并非所有测试都需要单元测试。编写外部测试脚本来测试 API 也是有益的。一种名为 Seleniumhttp://www.seleniumhq.org )的工具甚至可以帮助你实现浏览器自动化。

服务拆分

将单体应用分割成独立、松散耦合的小型服务是减少技术债务的好方法。

如果大型单体应用程序的技术债务直接扎根于应用程序的核心,那么处理起来就会很麻烦。在这种不稳定的基础上进行构建,日后很难拆分。不过,有一个解决方案;通过将新功能构建为独立的服务,您可以有效地构建一个具有稳定基础的新核心,摆脱旧的薄弱基础架构。然后,您可以使用 RESTful 结构将其与旧的单体和此类新服务进行互通。

这种结构可以让你在迁移到新的微服务架构的同时继续开发新功能。马丁-福勒(Martin Fowler)提出了一种称为 "抽象分支"(Branch by Abstraction)的系统,它可以让你以循序渐进的方式对系统进行大规模更改,从而在进行更改的同时继续发布。

第一步是捕捉客户代码的一部分与其供应商之间的交互;然后,我们可以修改代码的这一部分,使其通过抽象层进行相互通信。

然后,我们再对与供应商的交互进行修改。在这样做的同时,我们也借此机会提高了单元测试的覆盖率。一旦某个供应商不再被使用,我们就可以将客户端迁移到使用该供应商,然后删除旧的供应商。

完美的分阶段迁移

将单体应用拆分成独立的小型松耦合服务是减少技术债务的好方法,但在此过程中,显然会给架构层面增加额外负担。

在迁移数据或托管环境时,您可能会在这一过程中遇到困难。如果部署过程不重复,而且每次部署都是独一无二的(例如在不使用持续集成的环境中),情况尤其如此。

使用容器技术(如 Docker)可以让您更好地执行快速应用程序部署,从而加快部署速度,同时提高可移植性并简化维护。有些人可能会觉得其他技术(如 Vagrant)更适合自己;无论如何,所有这些技术都有一个共同点:基础设施即代码。

基础架构即代码是指通过代码而不是交互式配置工具来管理和配置计算基础架构的过程。我们希望能够在迁移之前对任何类型的迁移进行阶段性测试,并在执行迁移时重新运行确切的流程。

通过编写脚本,你可以像编写代码一样事先测试迁移。可以确保在实时服务器而不是暂存服务器上完成迁移时,减少出错的几率。

除此以外,如果部署过程中的某个因素日后会导致问题,迁移过程还可以用于反向工程,或查看决策的合理性。从本质上讲,它是软件部署过程中的一个工件。

在部署过程中,应尽可能提供更多的资源,包括部署代码的人员、将项目整合在一起的开发人员,以及在极端情况下负责向客户提供最新信息的通信人员。有了这些资源,就可以快速调试这些问题,但部署代码的人员必须发挥带头作用,协调何时需要这些资源,以防止分心。

按照预先计划好的正式例行程序工作,同时也为纠正任何问题留有余地,这通常有助于尽可能轻松地完成部署工作。