Day 1 分析和规划
Day 1 需求分析和计划
概述
软件开发的第一步是业务和需求分析,明确软件的使用场景,业务目标,并在此基础上总结处软件的需求。这一过程有各种名称,各种方法,取决于不同的软件,领域,内外部环境等。一般而言,承接的外部需求,或者需要对外分包的系统,需求分析的要求就很高。对于复杂的系统,变更代价高昂的产品,前期需求分析的要求也比较高,而对于简单的,企业内部的项目,软件需求分析通常没有太高的,过于形式化的要求。而对于需求变化充满了不确定性的产品,则需要考虑原型技术。我们这个系统本身是一个大的系统的一部分,有明确的上级需求输入,规模也不大,因此并不需要太正规和形式化的需求分析。需求分析和软件架构设计通常是耦合在一起的。尤其是,当规划者,设计者,实现者是同一个/批人的时候,这些工作通常是在一起进行的。
简单的来说,我们需要做下面的几项工作:
- 明确外部功能和性能需求
- 明确业务场景和使用模式
- 明确硬件设备约束
- 明确子系统间的接口
首先是明确外部需求和使用场景/模式。
外部需求一般是从产品规划处得到。通常,市场规划和产品分析会给出产品要实现哪些功能和特性,要达到多高的性能,可靠性,成本等要求。
使用场景与使用模式则需要和潜在的用户,领域专业人士进行沟通交流得到。
这里,比较推荐利用use case来分析需求的方法。use case,国内译作用例,用况,是Ivar Jacobson在1986年前后提出的技术。它是对系统如何反应外界请求的描述,是一种通过用户和使用场景来获取需求的技术。它是在不展现一个系统或子系统内部结构的情况下,对系统或子系统的某个连贯的功能单元的定义和描述。更详细的内容读者有兴趣可以去看一下机械工业出版社的《编写有效用例》一书。
比如,就我们的玻片扫描,我们通过阅读书籍和教材,了解到基本的操作要求,还需要和病理科医生沟通,学习和了解他们的工作方式和方法。比如,经过沟通和观察,我们得到了如下一些关键信息:
- 医生拿到一张玻片,会先看一眼玻片上涂层的形状,大致判断出应该在那里观察计数。对于手推的玻片,就白细胞而言,观察区域是涂片区域接近尾部的部分,在低倍镜下应该看到细胞单层,没有重叠。如果太靠近头部,血细胞厚度太后,细胞重叠,不利于区分和观察;如果太靠近尾部,白细胞很容易在推片的过程中被破坏,导致观察到大量无效的退化细胞。
- 白细胞的计数必须是一个连续区域内的所有细胞,不管是骨髓还是外周血。在这个区域中,不能有被遗漏的细胞。也就是说,不能挑着“好看”的细胞采集,以确保可信息
- 细胞的分类在很多时候还需要参考它的周围
- 对健康人的外周血涂片,白细胞是比较稀疏的,一般要移动几个百倍视野才能找到一个白细胞,而在骨髓涂片中,白细胞的密度会很高,通常一个百倍视野中能看到数个甚至数十个白细胞。然而,对于病患,则不是这样。例如,急性白血病的外周血中会有大量的白细胞存在,而化疗晚期病人,即使是骨髓,白细胞密度也极为稀疏,甚至找遍整张玻片都凑不到100个数目要求。
- 等等等等
这些信息都是进行需求分析的输入,基于这些信息,我们会形成我们的系统用例。
Use Case示例:单张手工扫描
例如,我们考虑一个简单的场景,用户手工进行一张玻片的扫描。我们最终整理出它的用例来。
用例描述有各种方法和工具。我们这里使用最简单的,文字描述的方式,只描述最重要的内容
Actor:
操作员
前置条件:
- 扫描仪已开机
- 扫描软件已经启动,已经与扫描仪连接,已经与后台服务器连接
- 扫描软件处于等待状态
操作步骤:
操作员手工将一张玻片放到载物台上
操作员在界面上启动单张扫描功能
扫描仪进片,拍摄玻片的标签图和涂片区域的整体照片
本软件分析标签图,寻找二维码并进行解析。如果是符合要求的二维码,可以从中解析得到样本编号(sampleID),样本类型(sampleType)(外周血还是骨髓),病人识别号(caseNo)。如果从标签中识别二维码失败,或者内容格式非法,则sampleType为默认值外周血,其他字段为空
弹出界面让用户输入或确认扫描的参数。包括:
- 样本类型
- 样本编号(Optional)
- 病例编号(Optional)
- 是否扫描各种细胞(白细胞,红细胞,巨核细胞)及目标个数
- 白细胞的扫描方式(红细胞和巨核细胞是固定的不可选择)
- 等等
用户确认之后,开始扫描。在扫描过程中,在本软件的界面上应当能够显示扫描的过程。包括动态的对焦和最终结果的显示。
- 若在前面用户指定了审核扫描区域,则弹出界面让用户修改十倍扫描的范围
- 在扫描过程中,扫描得到的照片在主界面上依次更新呈现
- 扫描的照片会被打包到zip格式的归档文件中
- 视野信息要能记录到索引文件中
软件对得到的十倍图进行分析,计算出要做百倍扫描的区域。如果是连续扫描,那么是一个或几个矩形的区域;如果是非连续扫描(外周血这种),则是每个要拍摄的位置的坐标。
- 若在前面指定了审核扫描区域,则弹出界面让用户审核和调整百倍扫描的范围。
- 这里,连续扫描时的范围指的是要扫描的区域(矩形区域)
- 非连续扫描时指的是扫描这个区域内的白细胞
- 若在前面指定了审核扫描区域,则弹出界面让用户审核和调整百倍扫描的范围。
执行百倍扫描。在扫描过程中,应当在主程序界面上连续显示对焦过程和得到的每一张照片
对于百倍扫描的照片,如果是非连续扫描,每张照片独立保存;如果是连续扫描,则要对这些照片做无缝拼接,并按照指定大小切割成小图片保存
扫描的所有图片和其他的信息最终上传给后台服务器做分析处理。
完成扫描之后,载物台立即退出,此时用户可以取走扫描完成的玻片,并放入另一张玻片进行扫描。而这个时候,上一张玻片的的拼接,打包,上传等工作应当在后台进行。即扫描工作不应当被数据处理阻塞
后置条件:
略
非功能需求和约束
软件除了功能需求之外,还有各种非功能的需求,比如性能需求,可靠性需求,可测试性需求,可维护性需求,部署需求,安全需求,成本需求,可获得性需求等等。这些需求通常无法在用例中表述,但同时又是项目成败的关键因素,并且对开发有着至关重要的影响。
比如,商品规格中有吞吐量规格,即全自动化模式下,一天能够扫描XX张标准玻片。针对这个需求,我们计算出每张玻片的全流程时间,其中,自动化送片机的送片,取片,扫描仪的进片,退片要化多少时间,这是死的时间消耗,硬件确定之后就无法改进了。剩下的时间是留给扫描仪的扫描时间。这个时间我们又进一步估计出低倍扫描的时间,高倍扫描的时间,这些时间够不够,如果不够,又能采取哪些方法来优化满足。
再比如,我们和医生沟通,了解他们的工作模式,了解他们如果使用我们的设备做扫描分析时,从开始扫描到得到结果能够容忍的时间。这个时间不仅仅是扫描的时间,还有上传,分析,结果输出的时间。我们首先要分析当前的硬件能否达到设计要求,如果不能,那么首要的问题是更改硬件选型或调整产品规格;如果硬件能够达到要求,就要分析软件设计是否受到性能约束的影响。比如是否要做多线程并发,等等。
再比如,我们需要和技术支持工程师了解设备进医院的流程,了解出现硬件问题后的处理流程。比如,我们得到如下的可靠性和可维护性要求:
- 要求玻片扫描过程中出现了异常(例如,对焦失败,找不到细胞)后能够正常返回,并继续下一张玻片的扫描
- 送片机要求连续取还片XX张无失败,要求取还片过程中损坏玻片的概率不超过1张/XX张
- 要求XX%的故障可以软件处理,XX%的故障可以通过简单的软件复位解决,XX%的故障可以通过设备上下电解决,XX%的故障可以通过现场操作人员通过插拔连线解决。需要支持工程师到现场的次数不能超过XX/XX,等等。
所有这些都会对我们的软硬件产生至关重要的影响,甚至会否决硬件方案的选型。因此必须在立项之初就评估清楚。
所有这些东西我们这里不再赘述。在后面,涉及到具体的特性时,我们会逐步考虑。
系统方案和软件架构的确定
明确了需求之后,就是确定软件方案和架构的时候了。
- 首先,这是一个本地应用程序,因为涉及到图像,硬件,AI,性能等因素,我们排除了所有的基于Web的方案。
- 因为商务等方面的原因,我们必须使用C++开发,排除C#/WPF/WinForm方案,因为开发人员技能原因,排除MFC,选择C++/Qt方案。
- 因为选择的扫描仪只有Windows下驱动,所以软件运行环境为Windows10/11中英文版
- 综合考虑成本等因素,电脑配置等级为i7级别,内存不低于16G,空闲硬盘不低于1G,最低N1660显卡,显存不低于8G
这些内容也会对最后的软件架构和实现方案产生影响。例如,硬件限制了内存不得大于16G,而400万像素的一张照片在内存中的大小大约为15M,我们可以大致估计出内存中最多同时存放500张照片,如果再保守一些(比如,医生会用这台电脑做别的事情),那么就是200-300张照片。而我们对玻片做10倍的连续全片扫描时,照片的数量是大约1000张,而对一个3mmx3mm的连续区域做百倍连续扫描时,得到的照片数量也大约是1000张这个数量级。所以,在处理算法上,我们必须实现图片的随用随放,1000张400万像素的细胞照片在中等图像质量的JPG文件大约占用1.5~2G的磁盘空间,按照1T的可用磁盘空间来算,只要不到500张玻片就会将整个磁盘耗尽,所以本地的临时图像数据文件需要自动清除,同时软件要能够识别磁盘空间不足的问题,并提示用户进行处理。甚至,它还会对我们采用机械硬盘还是固态硬盘产生影响。
讨论了这些之后,对于这些细节我们就不会再详细讨论了,我们会直接进行下一步的工作。在后面的开发过程中,涉及到上述内容时,我们再会讨论它的影响和对策。
所有这些内容完成之后,我们得到了软件的总体架构设计和模块划分。那么,接下来,才是我们的软件计划和开发工作。
制订开发计划
接下来需要制定开发计划。这里的“计划”,是一种很轻量级的计划,或称称为Build计划。它是一个自顶向下的分解结构,定义了软件的构建计划。例如,假定我们只需要考虑单张扫描和批量扫描两个基本功能,则顶层计划如下:
1 | graph LR |
对“单张扫描”,我们继续分解:
1 | graph TB |
基本项目结构的工作进一步分解为:
1 | graph TB |
<**预览图扫描**>功能可以分解为:
1 | graph TB |
真正的扫描活动分解:
1 | graph TB |
我们接下来就会按照上面的计划进行开发活动。