65.9K
CodeProject 正在变化。 阅读更多。
Home

学习如何编写好软件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.93/5 (27投票s)

2013年9月24日

CPOL

11分钟阅读

viewsIcon

94930

downloadIcon

481

本文将逐步指导您每次编写出色的软件。

引言

我已经为不同的客户开发软件很长时间了。我总是尽力编写出色的软件,满足所有客户需求,最重要的是,我很高兴我为客户交付了一份出色的工作。我总是努力定义什么是优秀的软件,以及如何每次都能交付出色的工作。但每个客户面临的挑战和客户需求都不同。期望水平也因客户而异。

弄清楚项目从何开始,以及如何交付令客户、开发人员和架构师都满意的软件,绝非易事。我们对优秀软件和编程进行了大量研究,但在编程时,大多数开发人员和架构师都忽略了这些规则,这给他们带来了麻烦。

在本文中,我们将研究什么是优秀软件,以及如何应用面向对象分析和设计规则来编写优秀软件。我们已经学习了封装、委托、解耦和其他面向对象实践,但我们如何在实际编程中使用它们呢?

什么是优秀软件?

这是一个模糊的术语,因为不同的人对它有不同的定义和含义。

客户说优秀的软件能做客户想要的事情,如果他以新的方式使用它,它仍然应该有效,不会崩溃并给出意想不到的结果。大多数客户不关心开发软件的架构和其他最佳编程实践。相反,他更关心软件应该给出的结果。崩溃的应用程序和奇怪的结果会激怒客户,并影响他对程序员的信心。

面向对象程序员说优秀的软件是用面向对象的方式编写的代码,它不重复代码。每个对象都负责处理自己的行为。在您的应用程序上应用适当的面向对象规则,使其坚固且易于扩展。您的结构应该更灵活,以处理新的更改。

架构师说优秀的软件是您使用适当的分层和设计模式的软件。您的软件包含可重用组件,因为重复解决相同的问题并不好。您的应用程序开放扩展,但封闭修改。组件应该松散耦合。

质量保证团队说没有软件是优秀的软件,因为如果他们认为软件是优秀的,那他们怎么能从中发现错误呢?这是我同事给我的一个有趣而有趣的答案。但客户和测试人员的想法几乎相同。优秀的软件能给出预期的结果,不会崩溃,如果他们想以不同的方式使用它,会给出正确的结果。最重要的是,它满足所有客户需求。

项目经理、董事总经理、客户经理和首席执行官等其他人的想法几乎相同,这些想法已包含在上述定义中。

我们从不同的人那里听说过优秀软件。当我们交付软件时,如何才能让所有这些人满意呢?在这个阶段答案并不容易,但您将在本文中学习优秀软件,这将增强您交付优秀软件的智力。

手机店项目

我的一个朋友开了一家手机店,他卖新旧手机。他以前手动管理纸质库存。有一天,他决定放弃纸质库存系统,想为他的商店使用一个自动化系统来管理库存和搜索功能,以帮助客户选择他们梦想的手机。他正在转向一个新系统,因为他想增加销售额并管理库存。他把这个项目交给了一家知名公司来构建自动化系统。几个月后,这家编程公司给了他一个他们为他构建的新电脑系统。让我们看看这家公司是如何为手机店设计新系统的。

手机店新应用

这家编程公司完全用一个新系统取代了手写笔记,以帮助客户搜索手机。他们提供了这个UML类图来展示他们所做的工作。

他们创建了两个类:`Mobile` 包含设备信息,`Inventory` 包含所有设备以及添加、搜索和获取手机的方法。

让我们看看编程公司编写的实际代码。

手机代码

`Mobile` 类,正如UML图中所示,包含手机设备的所有属性,它有一个构造函数,在创建对象时获取所有数据,并且它只公开属性的getter方法。

public class Mobile
{
    private string _iemi;
    private double _price;
    private string _brand;
    private string _model;
    private string _color;

    public Mobile(string ieme, double price, string brand, string model, string color)
    {
        _iemi = ieme;
        _price = price;
        _brand = brand;
        _model = model;
        _color = color;
    }

    public string IEMI{ get{ return _iemi; } }

    public double Price{ get{ return _price; } }

    public string Brand{ get{ return _brand; } }

    public string Model{ get{ return _model; } }

    public string Color{ get { return _color; } }
}

库存代码

Inventory 类包含所有手机的列表,并公开添加手机、获取手机和搜索手机的方法。

public class Inventory
{
    List<mobile> _mobiles = new List<mobile>();

    public void AddMobile(string ieme, double price, string brand, string model, string color)
    {
        var mobile = new Mobile(ieme, price, brand, model, color);
        _mobiles.Add(mobile);
    }

    public Mobile GetMobile(string ieme)
    {
        foreach (var mobile in _mobiles)
        {
            if (mobile.IEMI == ieme)
            {
                return mobile;
            }
        }
        return null;
    }

    public Mobile SearchMobile(Mobile mobile)
    {
        foreach (var m in _mobiles)
        {
            if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand != m.Brand)
                continue;
            if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model != m.Model)
                continue;
            if (!string.IsNullOrEmpty(mobile.Color) && mobile.Color != m.Color)
                continue;
            return m;
        }
        return null;
    }
}

SearchMobile 方法将在本文后面的进一步讨论中使用,它是手机店最重要的功能,因为客户应用程序将使用它为客户查找手机。客户可以根据品牌、型号和颜色进行搜索。此方法包含比较搜索中第一个合适设备的逻辑。

软件存在搜索问题

当手机店开始使用新的电脑系统搜索手机时,有时搜索系统没有返回任何结果,但店主知道他库存中有设备。因此,由于系统奇怪的结果,他开始失去客户。

编程公司交付的软件未能满足客户,客户带着投诉找他们。因此,编程公司交付的软件并非优秀软件。

我们如何才能每次都交付优秀的软件?

问题在于我们如何才能每次都编写出色的软件。有没有一套规则可以遵循,从而编写出色的软件?再次阅读客户、程序员和架构师的定义,并尝试定义一套优秀软件的规则。优秀的软件必须满足客户,它必须应用面向对象编程原则,并且应该松散耦合。我们可以将优秀软件的定义分解为三个步骤。

考虑到不同人的观点,我们总结出这三个主要步骤来编写优秀的软件。现在我们将把这三个步骤应用到手机店项目中,以编写优秀的软件。

软件符合客户要求

编程公司交付的项目未能满足客户。他们坐在一起,开始寻找搜索功能损坏的原因。当他们通过编写测试工具分析和测试应用程序时,他们发现问题出在字母大小写上。搜索模块以区分大小写的方式匹配产品。例如,手机店的库存中有“Nokia”,而客户使用字符串“nokia”搜索设备。

他们开始讨论不同的解决方案。一位开发人员说只需修复问题并再次交付,暂时不用担心设计。另一位开发人员说,我们不能交付再次导致其他问题的项目,所以我们必须足够聪明,以更好的设计修复问题。

他们都同意 “不要制造问题来解决问题”。这是处理事情的明智和正确方法。他们最终确定了以下解决方案。

  1. 每次字符串比较都应使用 ToLower,以避免下次出现问题。
  2. 对固定范围的值使用常量或枚举,以避免拼写错误和大小写问题。

软件的第一次改进

他们在软件中进行的第一次改进是为了避免字符串比较问题,即在比较语句中添加 ToLower() 并使用枚举表示颜色。

新的搜索方法包含小写字符串比较,并在需要时使用枚举。

public Mobile SearchMobile(Mobile mobile)
{
    foreach (var m in _mobiles)
    {
        if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand.ToLower() != m.Brand.ToLower())
            continue;

        if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model.ToLower() != m.Model.ToLower())
            continue;

        if (mobile.Color !=  MobileColor.None && mobile.Color != m.Color)
            continue;

        return m;
    }
    return null;
}

新搜索功能稳定,现在可以正常运行了。客户现在很满意,所以在这个阶段我们已经完成了第一个任务 “软件符合客户要求”。几天后,手机店想到,应用程序应该返回所有符合搜索条件的手机。通过这项新功能,他可以增加销量,并让客户从多个设备中选择一款合适的设备。

他提出了一个新的应用程序需求。搜索工具必须返回所有符合搜索条件的手机。编程公司很容易地将该逻辑加入其中。

public List<mobile> SearchMobile(Mobile mobile)
{
    List<mobile> toReturn = new List<mobile>();

    foreach (var m in _mobiles)
    {

        if (!string.IsNullOrEmpty(mobile.Brand) && mobile.Brand.ToLower() != m.Brand.ToLower())
            continue;

        if (!string.IsNullOrEmpty(mobile.Model) && mobile.Model.ToLower() != m.Model.ToLower())
            continue;

        if (mobile.Color != MobileColor.None && mobile.Color != m.Color)
            continue;

        toReturn.Add(m);
    }
    return toReturn;
}

编程公司以非常小的努力交付了软件,这让客户非常满意,因为它完全按照他想要的方式运行。它增加了销售额,他现在对软件很满意。

现在软件的表现正如预期。所以我们的第一个任务已经完成。

尝试应用一些面向对象原则

客户满意,软件运行正常。现在,让我们尝试应用一些面向对象原则,以增加一些灵活性来处理新的变化。当编程公司开始研究时,他们注意到搜索将Mobile作为参数,但它不使用IEMIPrice来过滤记录。这意味着设计有问题,我们必须重新考虑以进行改进。

他们提出了新的解决方案。Mobile 对象并没有达到其名称所暗示的功能。它也没有代表一个单一的概念。让我们尝试删除不直接属于手机的额外属性。我们创建一个新类 MobileSpec,并将所有与规格相关的属性添加到其中。系统的新UML图如下。

应用一些基本的面向对象概念后,代码现在清晰了许多,看起来也更灵活了。新的Mobile类现在是这样的:

public class Mobile
{
    private string _iemi;
    private double _price;
    private MobileSpec _mobileSpec;

    public Mobile(string ieme, double price, string brand, string model, MobileColor color)
    {
        _iemi = ieme;
        _price = price;
        _mobileSpec = new MobileSpec( brand, model, color);
    }

    public string IEMI{ get{ return _iemi; } }
    public double Price{ get{ return _price; } }
    public MobileSpec Spec{ get{ return _mobileSpec; } }
}

搜索现在也更清晰了。

public List<mobile> SearchMobile(MobileSpec mobileSpec)
{
    List<mobile> toReturn = new List<mobile>();

    foreach (var m in _mobiles)
    {

        var specs = m.Spec;

        if (!string.IsNullOrEmpty(mobileSpec.Brand) && 
                   mobileSpec.Brand.ToLower() != specs.Brand.ToLower())
            continue;

        if (!string.IsNullOrEmpty(mobileSpec.Model) && 
                   mobileSpec.Model.ToLower() != specs.Model.ToLower())
            continue;

        if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != specs.Color)
            continue;

        toReturn.Add(m);
    }
    return toReturn;
}

现在只需将新代码与旧代码进行比较。您会注意到,应用了一些基本的OOP规则后,新代码设计良好且更具灵活性。

可重用和松耦合组件

几个月后,客户回到这家编程公司,要求他们为手机添加一些额外的字段。因为现在市面上有各种各样的智能手机,它们有内存、屏幕分辨率和操作系统。

编程公司接受了修改,并开始考虑在 MobileSpec 文件中添加这些新字段。但他们很快注意到,需要在三个不同区域的类中进行更改。

  1. 在 MobileSpec 文件中添加字段。
  2. Mobile 类的构造函数上添加参数,并在代码中将这些参数传递给 MobileSpec
  3. Inventory类的Search方法中,添加新字段进行压缩。并更改Inventory类的Add方法。

这算是一个好的设计吗?

优秀的软件拥有可重用组件,并且开放扩展,封闭修改。但现有的手机项目结构中不具备这两个特性。这意味着每当我们在 MobileSpec 类中添加新的规格时,我们需要再次更改所有其他类。这真是一团糟。

我们如何使其松耦合?每个类都应该自己完成自己的职责,而其他类不应代表任何类执行工作。观察Inventory类中Search方法的代码。它在其中比较了MobileSpec对象。这意味着每当我们更改MobileSpec类时,都需要更改Search方法。所以我们的设计是紧耦合的。

他们做的第一件事是,从Inventory类中删除MobileSpecs比较,并在MobileSpec类中编写一个新的Equal方法,并将所有比较逻辑放入该方法中。

public class MobileSpec
{    
    public bool IsEqual(MobileSpec mobileSpec)
    {
        if (!string.IsNullOrEmpty(mobileSpec.Brand) && mobileSpec.Brand.ToLower() != Brand.ToLower())
            return false;

        if (!string.IsNullOrEmpty(mobileSpec.Model) && mobileSpec.Model.ToLower() != Model.ToLower())
            return false;

        if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != Color)
            return false;

        return true;

    }
}

Inventory类中的Search方法现在干净多了,它没有做任何属于MobileSpec类的额外工作。MobileSpec类的更改不会影响Inventory类的Search方法。

public class Inventory
{
    .
    .
    .

    public List<mobile> SearchMobile(MobileSpec mobileSpec)
    {
        List<mobile> toReturn = new List<mobile>();

        foreach (var m in _mobiles)
        {

            if (m.Spec.IsEqual(mobileSpec))
            {
                toReturn.Add(m);
            }
        }

        return toReturn;

    }
}

现在他们认为没有必要在Mobile类的构造函数中为MobileSpec类单独设置参数。只需将MobileSpec作为参数,即可消除依赖关系,使设计尽可能地松散耦合。

public class Mobile
{

    private string _iemi;
    private double _price;
    private MobileSpec _mobileSpec;

    public Mobile(string ieme, double price,MobileSpec spec)
    {
        
        _iemi = ieme;
        _price = price;
        _mobileSpec = spec;
      
    }
    .
    .
    .
    .
}

有了这个改变,Inventory类的AddMobile方法也将随之改变。在这里也需要将MobileSpec作为参数传递。

public void AddMobile(string ieme, double price,MobileSpec spec)
{
    var mobile = new Mobile(ieme, price, spec);
    _mobiles.Add(mobile);
}

现在,如果你查看解决方案,设计是灵活和可重用的。你可以独立使用MobileSpec类,它封装了所有属性和方法。该设计具有松散耦合的组件,并且Mobile类的更改不会影响其他类。

现在让我们在 MobileSpec 类中添加新字段。您会注意到,其他类将不需要任何更改。您只需要在 IsEqual 方法中添加新字段、属性和比较语句。

public class MobileSpec
{
    private string _brand;
    private string _model;
    private MobileColor _color;
    private int? _internalMemory;
    private string _operatingSystem;
   

    public MobileSpec(string brand, string model, MobileColor color)
        : this(brand, model, color, null, "")
    {
    }

    public MobileSpec(string brand, string model, MobileColor color, 
           int? internalMemory, string operatingSystem)
    {
        _brand = brand;
        _model = model;
        _color = color;
        _internalMemory = internalMemory;
        _operatingSystem = operatingSystem;
    }

    public string Brand{get{ return _brand;}}
    public string Model{get{ return _model;}}
    public MobileColor Color{get{ return _color;}}
    public int? InternalMemory{get{ return _internalMemory;}}
    public string OperatingSystem{get{ return _operatingSystem;}}


    public bool IsEqual(MobileSpec mobileSpec)
    {

        if (!string.IsNullOrEmpty(mobileSpec.Brand) && mobileSpec.Brand.ToLower() != Brand.ToLower())
            return false;

        if (!string.IsNullOrEmpty(mobileSpec.Model) && mobileSpec.Model.ToLower() != Model.ToLower())
            return false;

        if (mobileSpec.Color != MobileColor.None && mobileSpec.Color != Color)
            return false;

        if (!string.IsNullOrEmpty(mobileSpec.OperatingSystem) && 
                     mobileSpec.OperatingSystem.ToLower() != OperatingSystem.ToLower())
            return false;

        if (mobileSpec.InternalMemory.HasValue && mobileSpec.InternalMemory != InternalMemory)
            return false;

        return true;

    }
}

恭喜!你已经编写出了优秀的软件

这些是一些基本规则,遵循它们,你就能每次都写出优秀的软件。只要养成习惯,享受你的工作吧。

© . All rights reserved.