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

软件项目开发与决策制定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (68投票s)

2018年2月13日

CPOL

23分钟阅读

viewsIcon

59986

在启动和运行软件项目时做出决策。

本文内容

对于参与项目决策的人员来说,了解他们的决策对项目成功以及时间成本和金钱成本的影响非常重要。

在我20多年的软件开发经验和10多年的咨询经验中,我作为架构师或开发人员参与了许多项目——其中大部分取得了成功,有些则失败了,但每个项目(无论成功与否)都涉及不同人员做出的良好和糟糕的决策。

本文的目的是通过倡导根据我的经验有用的决策并避免错误的决策,为项目的成功奠定基础。

总的来说,我有C++、Java、C#和JavaScript的经验,但在过去的10年里,我主要从事C#桌面应用程序的开发。尽管如此,这里提出的许多想法都是通用的。当我讨论C#桌面应用程序时,我会明确说明。

软件开发中总是存在“政治因素”——本文不涉及此。本文的目的是提供能够最大程度提高客户满意度,同时最大程度降低开支的建议。

项目开发主要指南

在此,我将介绍软件开发的两个主要指南,本文后续将更详细地描述它们。

  1. 关于软件开发主题的书籍、文章甚至广告垃圾多如牛毛。对所有这些都持保留态度!如果您读到的内容与常识相冲突——请选择常识。
  2. 在公司、项目或个人层面,需求的传播(尤其是API需求)应该从使用者(客户端)流向构建者,而不是反过来。客户端,我也指同一项目中其他开发人员使用的软件的开发者。这条非常重要的规则经常被忽视,从而损害了项目的成功。

项目启动时

在项目开始时需要做出一些重要决策。理论上,项目开始时做出的错误决策可以在以后纠正。然而,在错误决策上浪费的金钱或时间越多,它们在以后被纠正的可能性就越小,因为纠正它们会引发能力问题。

选择语言

对于语言,我强烈不推荐C++(除非您正在构建一些对毫秒级至关重要的高性能算法),主要原因是其编译时间缓慢。所有软件开发(如果做得恰当)都涉及大量的原型制作、应用程序重启或运行应用程序测试。每次代码更改后的新运行都涉及编译。C++模板的构建方式是编译时必须进行代码生成,因此C++通常编译速度非常慢。编译缓慢将导致原型制作缓慢,进而导致开发缓慢。没有模板,C++就不像现代强类型语言那样强大。

此外,Java和C#内置了许多标准库,而C++依赖于第三方库。

最佳团队规模

根据我的经验,小团队能更快地开发出更好的软件。例如,对于桌面三层项目,我建议组建一个3人团队——每层一人 + 一名架构师 + 1-3名备用人员。

我最成功的项目拥有的正是这样的团队,成员人数在3到7人之间。我参与的最不成功的项目之一,团队成员超过20人,几年内无法完成小项目几个月内就能完成的任务。

使用Scrum还是不使用Scrum

毫无疑问——使用Scrum!敏捷/Scrum可能不完美,但它比其他替代方案更好。然而,Scrum的某些方面并不有效(我认为它们不应该成为流程的一部分,尽管大多数敏捷教科书都会提及它们)。以下是Scrum的积极和消极特征列表。我的建议是:避免使用下面列出的消极特征。

Scrum的优点

  1. 通常,在项目开始时,客户自己可能对他们需要什么没有清晰的愿景。客户可能需要多次迭代(包括客户和开发人员的错误)才能弄清楚需要构建什么,而开发人员才能弄清楚如何构建它。Scrum的迭代方法最适合这种范式。
  2. 在Scrum下,开发人员可以自行估算实现某个功能所需的时间——他们通常比经理做得更好。
  3. 每日更新对经理和开发人员都有益。双方都能更好地了解项目的进展。此外,对于开发人员来说,这是一个与他人分享他们一直在做什么、了解他人在做什么的机会,总的来说,锻炼大脑的非编程部分,同时让编程部分稍作休息是很好的。
  4. Scrum会议的结束,包括演示和规划,如果进行得好,也同样有益,原因与每日会议相同,但规模更大。

Scrum的缺点

  1. Scrum的某些版本主张只跟踪整个团队的绩效——而忽略了个人开发者的贡献。虽然团队绩效非常重要,但认可个人努力仍然很重要。
  2. 在Scrum结束时会有一些会议,人们在会上捶胸顿足地说哪里出了问题——越多越好。我从未见过从中产生任何好的结果。当出现问题时,应该在开发人员之间解决。更严重的问题应该上报给架构师/经理,有时需要对项目流程进行更改。

结对编程

我从未见过结对编程能带来任何好处,除非它是短期且完全自愿的,由两个自愿的伙伴为分享知识而进行。

我实践的是非常积极、快速、以结果为导向的编程——看别人编程让我犯困。我认识的所有被迫进行结对编程的人都认同我的观点。

选择包和第三方组件

在选择第三方库时,您应该非常小心——您应该对那些华丽的演示和广告持怀疑态度。只有当以下条件之一为true时,才选择第三方包(无论是开源的还是需要付费的):

  1. 这是一个知名产品——比如MS Visual Studio、Oracle DB、MS SQL Server、Telerik或DevExpress控件——拥有**数千**条用户证言。
  2. 您信任的、将直接使用此软件的开发人员,已经对该软件进行了合理时间的试验(至少两周),并且对使用它感到满意。

请记住,您为软件支付的费用越多,将来替换或淘汰它就越困难,因为那些您花钱的人会希望确保钱没有浪费。

请记住,大公司仍然可能拥有糟糕的产品。我多年前所在的一个团队,在Oracle的Stellent软件上遇到了负面经验——他们为此花费了大约25万美元!值得称赞的是,Oracle当时刚刚收购Stellent——所以它完全是在Oracle之外开发的。

另外,请记住,前期花一个月时间研究第三方组件,日后可能会为您节省数年时间。

关于第三方组件的一些具体建议

本节倾向于微软桌面生态系统。

UI控件包

对于WPF前端,我推荐以下包

  1. Telerik - 在设计时充分考虑了WPF概念,易于修改和学习。
  2. DevExpress - 个人认为WPF导向性较弱,因此定制起来不那么容易,但仍具有灵活性且速度相当快。它拥有Telerik所没有的一些控件,包括允许用户在运行时组装WPF视图的设计器控件和地理地图控件。

我对Xceed了解不多,但据我所知,它也相当好用。

我建议谨慎使用Syncfusion——至少几年前,它的构建方式与WPF的理念相悖。

我建议不要使用Infragistics——它开箱即用的功能最多,但根据我的经验,它很难定制,而在WPF中,定制才是关键!

上述所有WPF包在图表方面都存在性能问题。我使用并强烈推荐的WPF图表包是SciChart

无论您选择获取哪个软件包——请选择包含下载源代码权利的许可证。每当我使用Telerik或DevExpress时,我都需要研究它们的代码,以便能够实现客户所需的效果。

IoC 容器

大多数规模适中的项目都应该包含控制反转,以便为项目构建扩展点,或方便将实际实现替换为测试实现(例如,在针对后端测试前端功能时)。

我最常使用的是MEF2,它有点复杂,但总是足以满足项目目的。

我听过很多关于Ninject的正面评价(尽管我自己从未使用过)。

我正在开发Roxy IoC容器。它仍在开发中,尤其是在IoC方面,所以我现在还不能推荐它。希望几周后我能推荐它。

测试框架

毫无疑问,Xunit是我所知的最好的测试框架,我强烈推荐它。它是免费的。它允许使用各种基于属性的输入运行相同的测试。在其他框架中,您需要为每种输入组合编写单独的代码。

中间层

在微软生态系统中,我建议使用MVC控制器、WebApi或WCF构建自定义中间层。我也喜欢在中间层使用ADO Entity Framework。

WCF比ASP中间层解决方案更复杂、更强大。通常,ASP中间层对于目的来说已经足够了,但对于更复杂的需求,例如使用TCP连接而不是HTTP,WCF非常棒。

后端

对于规模适中的项目,我建议使用Oracle或MS SQL Server。根据我的经验,NoSQL数据库最多是作为SQL数据库的缓存机制,或者用于某些特殊操作,例如全局搜索。

项目工具

我使用过许多工具,以下是我推荐的:

对于源代码控制,我推荐Git和Subversion;两者都运行良好且都是免费的——我更喜欢Git。

作为需求/错误跟踪系统,我推荐Atlassian JiraRally。两者都是高度可配置、经过充分测试的解决方案。

对于构建、发布和持续集成,我推荐Atlassian Bamboo

请注意,当用户数量少于10人时,Atlassian软件非常便宜。对于这样的团队,每种产品每月统一收费10美元。

进步派与保守派

团队中总有一些渴望并愿意学习新软件和新软件包的人(我就是其中之一),以及那些想坚持使用他们熟悉且以前对他们有用的旧软件和旧软件包的人。

以下是一些这些方法被证明是正确的例子。

当进步派正确时

  1. 很久以前,从C++切换到Java极大地提高了项目的生产力,原因在于编译时间缩短和原生包的使用。
  2. 转向WPF和C#无疑是一个伟大的决定——WPF是最好的基于Windows的桌面开发包,而C#现在比Java更强大,并且正在不断改进。
  3. 人们对响应式扩展(Reactive Extensions)的采用速度较慢,但它绝对是一个出色的软件包,可以构建许多以前过于复杂而无法构建的东西。
  4. Roslyn是一个出色的C#编译器即服务,我相信它最终将使人们能够实现自己的语言功能,同时提供完整的IDE支持。

当保守派正确时

在过去的10年里,微软发布了许多失败的软件包。(这让我学会了谨慎。)

  1. 我仍然对Silverlight耿耿于怀。我认为它是一个很棒的Web开发包。我赌在了Silverlight上(尽管我明白这是一种微软的慈善行为,因为它正在损害自己的平台),结果我输了。
  2. Windows RT、多个Windows Phone平台,曾被微软积极推广,但都失败了。(我当时已经更明智了,从一开始我就对它们感觉不佳)

关于预测一个软件包何时会失败或成功的一些想法

恐怕几乎不可能百分之百确定地预测。

WPF是一个很棒的软件包,我第一次看到它的概念就爱上了它(我以前有很多类似的想法)。

Silverlight也是一个很棒的软件包,完全由微软维护,当微软决定不再需要它时,它基本上就死了。

Windows RT是一个彻底的失败,我从一开始就觉得它会失败,因为他们背离了.NET,试图转向原生。

我想经验法则就是

  1. 当微软发布一款优秀产品并大力推广时——产品就会蓬勃发展。
  2. 当微软发布一款优秀产品并停止维护时——产品就会消亡。
  3. 即使是微软的全力以赴也无法拯救一个糟糕的产品。

我想用几个关于微软新包的预测来结束这一小节。

基于我有限的信息,我相信Xamarin拥有辉煌的未来——它有望成为智能手机和平板电脑编程的主要方式之一。

我担心UWP不会流行,因为它只适用于Windows 10和Windows 10产品。您无法在Windows 8或Windows 7上运行它,更不用说Linux或Mac了。如果它只是WPF的一个苍白影子,而WPF可以在任何Windows平台上运行,为什么要使用它呢?

我以前错了,我可能还会再错——时间会证明一切。

项目开始前或初期进行原型设计

尽管敏捷具有迭代性,且客户在项目开始时可能只对最终产品有一个模糊的概念,但项目架构师在项目开始时至少提出一个小型原型仍然是一个好的做法。

这样的原型应该是一个端到端的系统,包含一些UI、中间层和数据库功能,以及基本的测试项目。应用程序的所有部分都应该可操作并相互通信,并且测试应该能够运行。

根据我的经验,我建议花费两周到一个月的时间来制作原型。架构师应该独自完成,只有在架构师要求时才寻求他人的帮助。

项目的其余部分将涉及扩展原始原型。

除了作为最初的参考点,这样的原型将使经理和团队确信他们所做的决策和使用的第三方组件将实际实现最终目标。

三层架构

这是标准的三层架构图示

在图表中,双向黑色箭头表示从UI客户端到中间层以及从中间层到数据库的请求-响应连接。

单向红色箭头表示从实时数据源到中间层和客户端(订阅者)的“热”(实时)连接。此外,它还表明一旦接收到实时数据,它可能会被记录到数据库中(从中间层到数据库的红色箭头)。

中间层和单个客户端可以有缓存以提高速度。通常,您会缓存已知在一定时间段内不会更改的数据。当时间段过去后,您会强制数据离开缓存或将其标记为“陈旧”,以便在下次请求后刷新数据。

重要提示:根据我的经验,最好的中间层是不包含任何业务逻辑的中间层。业务逻辑应该存在于数据库和UI中。中间层应该是一个高度可配置的通用网关,用于访问数据库基本功能,并且不应该在每次业务逻辑扩展时都需要修改。在我的人生中,我曾多次用C#和Java构建过这样的中间层。

也许次好的中间层是在添加新业务逻辑时只需要进行少量标准化修改的中间层。我也曾多次使用过这样的中间层。

运行项目

客户端-服务器API需求传播原则

多层开发的主要原则是,客户端对它使用的API拥有比实现者(API应该由客户端驱动)更多的发言权。

正如软件的客户端(或客户端代理,例如产品经理)对最终产品的外观拥有更多发言权一样,UI开发人员也应该对他们使用的来自中间层和后端的API拥有更大的发言权。

我想澄清两点

  1. 我并不是说UI开发人员完全决定了API。API应该在请求方和实现方之间的讨论中确定下来——有些事情甚至可能无法实现或非常难以实现。
  2. 服务器的实现仍然由服务器开发人员负责——我只讨论客户端所需的公共API。

UI开发人员与后端/中间层开发人员之间的关系应完全与最终用户和UI开发人员之间的关系相同。

这个原则经常不被遵循。事实上,人们从后端和中间层开始开发,然后UI必须解决所有由此产生的API问题。

想象一下,UI开发人员来到最终用户面前,告诉他们他构建的东西非常适合他们的需求,尽管他在没有与他们协商的情况下构建了它。这听起来相当荒谬,然而UI开发人员却经常遇到类似的情况,当后端开发人员告诉他们现在他们拥有所需的一切,尽管他们在没有与UI开发人员协商的情况下构建了它。

我的经验一次又一次地证实了这一原则——需求、API甚至开发都应该从消费API的部分传播到实现API的部分。这不仅适用于客户端-服务器划分,也适用于您自己编写一个重要的软件片段时。您需要从软件的目的开始,然后填补“空白”。否则,如果您从“空白”开始,您可能会发现它们并不完全符合目的。

测试驱动开发的流行是这一范式的又一印证。

创建API

在旧的“瀑布”哲学中,架构师试图在项目开始时创建许多接口,以便开发人员可以对着它们编程。从我的角度来看,这不是最好的方法——它本末倒置。

在项目开始时应该只创建很少的接口,并且架构师应该随时准备修改它们,如果发现它们不符合目的。

每当需要时就应该创建接口——例如,每当有一个方法或类需要处理满足特定接口但实现可能不同的对象时。不要过分努力地预见您需要一个接口——首先(当只需要一个实现时)对着一个类编程,但当需要时,准备好将其更改为接口。

这在个人层面和架构师层面都是如此。敏捷的迭代性质能够适应API修改。

架构师的角色

这让我想到软件架构师的角色。

架构师的角色(尤其是当项目规模适中时)可以由团队中的一名开发人员承担。

以下是软件架构师应该承担的任务

  1. 监督发布流程——将产品交付给用户或用户代理。
  2. 密切关注产品开发进度和产品需求变化。
  3. 在项目开始时——如前所述,构建一个非常精简的产品骨架(原型)(包括初始测试项目)。
  4. 解决不同开发人员之间的软件问题。
  5. 找出代码的共性,并将其提炼成一个可以在不同地方被不同开发人员使用的公共API。

最后一点也应由个人开发人员在其自己的代码中实践,或者如果开发人员在多个开发人员领域发现代码中的共性,他应该将其提交给共同考虑,并让架构师决定是否需要将该共性提取出来。

总的来说,个人开发人员和架构师都应该持续重构代码中的共性。

有些人认为他们需要提前找到并提取所有共性。这不太可能。没有人能做到,就像没有人能在项目开始时创建所有接口一样。没有理由在开始时花费大量时间思考个人或架构师层面的共性。在代码开发过程中,您将能够找到要重构的代码。

我对架构师的建议是,在多层架构中始终从数据形状的角度进行思考。在关系数据库中,数据通常放在规范化的表中。还有一些记录可能通过实时数据源传来。在该级别没有层次结构。然而,在UI侧,大多数记录都需要一些层次结构,以便以用户想要的形式显示给用户。我计划撰写另一篇文章专门讨论数据形状和数据形状转换。

团队建设

根据我的经验,工作场所以外的非工作相关活动是建立人与人之间信任,并让人感觉他们是一个团队的好方法。以下是一些有效的活动:

  1. 寻宝游戏
  2. 出去吃饭或喝酒
  3. 足球比赛
  4. 保龄球

人员问题

以下是我在项目过程中观察到的一些与人员相关的问题

  1. 良好的领导能力和政治手腕不足以运营一个软件项目。事实上,我见过一些项目失败,原因在于领导者拥有良好的领导能力和政治手腕,但对软件开发一窍不通,或者虽然对软件开发有所了解,却不敢用来反驳上级。
  2. 在会议上自信发言的人不一定了解软件细节。重要的决策绝不应该在一次会议上做出——只有经过彻底的书面讨论之后才能做出。

我观察到一种所谓的“江湖郎中”问题,即那些一无所知的人试图接管某个编码领域或项目领导。他们可能拥有良好的领导和演讲技能,其中一些人甚至可能拥有技术技能,但却是在不同的领域。不应该允许这样的人在他们一无所知的领域做出决策。

检验一个人是否某个领域的专家的一种方法是要求他构建一个概念验证原型。如果他无法构建一个小型概念验证,就不应该允许他接管项目。

我曾与一个人共事,他声称构建原型会花费太多时间,要么所有人听从他的领导,否则整个项目就会崩溃。这种“全有或全无”的方法是江湖郎中的确切标志——总是应该能够构建一个小型概念验证。这个人不得不离开项目,项目在他离开后成功了。

面试候选人

在我的职业生涯中,我主持了许多面试,以下是一些我想分享的观点:

  1. 切中要点——如果你想聘请Angular专家,不要问他太多关于WPF或SQL的问题。
  2. 测试概念,而不是细枝末节,例如,测试一个人对关系数据库概念的理解总是好的,但是(除非你专门聘请SQL专家),你不需要测试他关于MS SQL Server中临时表的知识。
  3. 有时记忆力好的人可以死记硬背很多信息,然后在面试中使用。这就是为什么让人们实际来做简单的编码考试很重要。这样你总能看到这个人是否有动手实践的知识,还是他只是在几天前突击记忆了所有东西。

编写、执行和测试需求

需求应该在开发人员和需求订购者之间确定下来——可能是最终用户、架构师或团队中的另一位开发人员。由于开发人员承担着责任,因此开发人员在开始工作之前,应始终仔细阅读需求,确保面面俱到。

需求(或Jira任务)通常应该围绕一个单一功能来编写。该功能可以是用户功能,也可以是最终用户永远不会看到的内部功能,例如一些重构;无论哪种方式,大多数情况下,一个需求应该对应一个单一功能。

一旦需求创建,不应向其添加新的功能请求。它应被用作从请求者到质量保证(QA)的路线图。如果开发人员实现了需求,但需求是错误的或不完整的——这是请求者的错误,应开启一个新的Jira任务来处理剩余部分。然而,如果开发人员未能满足需求——这是开发人员的错误,应重新开启该需求并重新分配给同一开发人员以完成。

在UI开发方面,也有一些小的、易于实现的需求(我称之为“UI小细节”),这些都可以归入一个单独的Jira任务,即使它们可能涉及完全不同的功能。您可以为它们打开特殊的“细节”Jira任务,在这种情况下,“单一功能”条件并非必需。

结论

在本文中,我根据自己作为架构师和开发人员的长期经验,为启动和执行多层项目提供了建议。

我相信遵循这些建议可以极大地降低项目在时间和金钱方面的成本,并增加最终产品的价值。

致谢

我谨感谢我亲爱的妻子Pazit审阅并编辑了这篇文章。

© . All rights reserved.