记一次百万行WPF项目代码的重构记录
此前带领小组成员主导过一个百万行代码上位机项目的重构工作,分析项目中存在的问题做了些针对性的优化,整个重构工作持续了一年半之久。
主要针对以下问题:
1.产品型号太多导致代码工程的分支太多,维护时会产生非常多的重复性的工作。
这是一个历史遗留问题,公司成立之初的开发人员在开发时没有考虑到后期其他机型的合并而留有余地,后面增加其他机型而导致的代码差异,是直接通过创建工程的分支来进行维护。两个不同机型之间可能大部分的业务逻辑都相同而只有少部分的界面和业务存在差异性,当修改一个共有的bug时,需要在所有分支上面都修改一遍。
同时有可能因为分支代码的改动,而无法直接进行代码合并,进而导致重复工作量的增加。如果分支不多的时候倒还好,而当机型越来越多,分支越来越多,这个重复性的工作会消耗相当大的时间和精力。所以合并所有机型的代码,实现上位机代码在不同机型上的通用性势在必行。
一个良好的软件架构应当是 面向接口编程,而不是面向实现编程。对于不同机型中的业务和流程在主体上是一样的,只是其中某些细节存在差异性的分支,所以我们需要将业务代码提炼出主体流程和对应的接口,通过这些业务接口来实现机型差异所带来的的业务流程上的差异和分支,而非工程上的分支。
在软件运行时,可以通过相应的配置来决定业务接口的具体实现是哪一个,同时由于不同机型的接口实现是分离的,修改一个机型不会影响到另一个机型的代码。当然,在修改主流程代码时候就要小心了,需要考虑这一个改动对所有机型的影响。除非是非常有把握的情况下直接改动,否则还是先抽象出接口保证原主流程代码不变,只修改你需要修改的实现。
2.配置零散,没有统一的管理机制,不利于打包。
软件中的配置数据保存的地方太零散,有保存在数据库的,有保存在txt文件的,有保存在注册表的,有保存在app.config的。经过不同开发人员的不断迭代,积累了很多无用的配置数据,并且没有人敢删除。而售后有时候为了查找修改某个配置需要在各个地方查找,非常繁琐。
同时也因为公司机型太多导致很多配置数据在不同机型上存在差异性,这些差异性有可能是来自硬件差异,也有可能是来自软件功能上的差异。而每发布一个版本,就有可能需要同时打包多个机型的软件包,每一个软件包至少有1个G 的大小。而随着机型的越来越多,一个版本不同软件包也会越来越多,这对于打包人员来说是个不小的负担,同时也要求打包人员需要明确的知道每一个机型配置上的差异性来保证软件包的正确性。
为了解决这个问题,我们花了数个月的时间,对所有机型保存在不同位置的配置做了一个整理。我们整理出了所有机型通用的配置,统一保存在数据库表中,同时为每一个机型建立数据库表用来保存存在差异的配置数据。为了方便打包人员和售后管理查看这些数据库,我们开发了一个配置管理工具用来专门查看和修改配置。
另外,我们为了解决多个软件包的问题,把所有硬件相关的配置整合进一个文件中,并开发出一个版本升级软件。在这个版本升级软件中,售后可以选择机型对应的硬件,升级程序可以通过所选硬件对应的配置写入到数据库中来实现同一个软件包不同机型的升级工作。
同时我们开发了通用的http接口给上层C#程序和下层C++程序使用,用于读写数据库的配置数据。
由于我们的设备有多个PC,并且在医院内部无法连接外网,此前软件升级时每个PC都需要售后人员拷贝软件包并手动调用升级脚本来完成升级。而现在,我们的升级程序可以通过远程调用的方式来同时完成多个PC的升级工作,做到了一键升级功能。
基于第一点的软件代码合并,在本次配置优化之后,打包人员每次打包仅需要一个软件包,即可实现不同机型的一键升级,省时又省力。
3.UI和业务逻辑混杂
项目以WPF为主,整体使用MVVM框架。项目中没有使用开源的控件库,其中含有非常多的高度自定义控件的开发,这些控件的UI表现代码和业务逻辑代码夹杂在一起,耦合性太高非常不利于理解业务代码和后期维护。
WPF的MVVM框架本身最大的优势就是为了分离业务和UI,降低耦合性,提高可重用性,所以这个项目并没有发挥出MVVM 框架的优势。针对这个问题,我们分离UI和业务,提炼出一个单纯的UI库,这个UI库不包含任何的业务代码,除了.Net Framework的依赖库之外,不依赖项目中的任何其他库。
每一个UI控件暴露自定义的依赖属性,在业务逻辑调用时,通过绑定这些依赖属性来改变UI的表现逻辑。这样做的好处是完全分离了UI表现代码和业务逻辑代码,并且这个UI库具有高重用性,可以交给其他项目使用,甚至可以直接开源出来。
而且在后期维护时,UI代码的改动与业务代码的改动互不干扰,也有利于Bug的排查。
4.由于前期开发人员的层次不一,并且没有CodeReview机制来保证代码质量,导致代码存在很多低级错误。
项目中存在大量重复代码,明明可以提炼出一个简单方便的方法,偏偏要在各个地方不断的Copy相同的代码。
明明只要增加一个参数就可以合并成一个方法,偏偏要写上几十个方法,诸如xxxx1,xxxx2....xxxx30,三十几个方法执行同样的功能,只有一个参数上的差异。我们的业绩考核又不是看代码量,这种代码看了让人啼笑皆非。
没有统一的命名规则,有些属性首字母小写,C#和C++风格混用,有些命名直接使用缩写,类似“ggr”这样缩写,除了作者谁能看懂这是什么意思?
一个类,一个方法代码过多,几千行一个类的文件不在少数,一个类承担的功能也过多也过余复杂。类和方法都应该遵循职责单一原则,不过在实际开发过程中单一原则不太好把控,但应该合理的控制代码行数。
我们在项目重构工作之前,制定了统一的命名风格,并严格限制了每个类每个方法的代码行数。一个文件,一个类,最多不超过一千行,一个方法最多不超过六十行。
六十行代码一个屏幕很难放下,这个要求其实比较低了,最合理的应该是三四十行,一个屏幕正好可以看完一个方法的所有代码。
在项目重构的过程中,我们规定所有人提交的代码都必须提交给高级工程师CodeReview,高级工程师之间互相CodeReview。每个人的知识面都是有限的,但可以通过合作来达到无限的广度和深度,我们应该尽量去避免犯一些低级的、显而易见的错误。
5.软件需要显示处理大量的图片,导致程序内存和CPU占用过大。
由于我们的软件需要处理显示大量的Dicom文件,并且对这些展示的图片都有较为复杂的操作,诸如旋转,放大、像素提取、锐化等,这些功能都集成在一个ImageControl中。然而由于我们的ImageControl写的不合理,读取一张500K的Dicom并显示会至少占用2M的内存。如果同时读取上百个Dicom文件,程序内存可以轻轻松松突破1个G,同时由于我们系统中不止一个程序需要处理这些图片,所以对系统的内存要求非常高。
在前期,我们只能不断的累加硬件,把内存扩展到了32G,甚至是64G,然而这只是饮鸩止渴的错误方式,应该从根本上解决ImageControl控件占用内存过大的问题,同时应该优化程序显示排列图片的逻辑。
在考虑到项目人员的分配情况和项目计划,我们决定优化ImageControl这个思路暂缓,因为这个工作量会更大。我们决定先着手优化程序展示图片的逻辑。程序只加载用户能够看到的Dicom,当用户下拉进度条时,再陆续加载可见的图片,并且将其余不可见的ImageControl销毁,优化之后程序占用的内存锐减60%-70%,可以接受。
记一次百万行WPF项目代码的重构记录的更多相关文章
- 年度巨献-WPF项目开发过程中WPF小知识点汇总(原创+摘抄)
WPF中Style的使用 Styel在英文中解释为”样式“,在Web开发中,css为层叠样式表,自从.net3.0推出WPF以来,WPF也有样式一说,通过设置样式,使其WPF控件外观更加美化同时减少了 ...
- WPF入门教程系列(一) 创建你的第一个WPF项目
WPF入门教程系列(一) 创建你的第一个WPF项目 WPF基础知识 快速学习绝不是从零学起的,良好的基础是快速入手的关键,下面先为大家摞列以下自己总结的学习WPF的几点基础知识: 1) C#基础语法知 ...
- IntelliJ IDEA 乱码解决方案 (项目代码、控制台等)
IntelliJ IDEA 乱码解决方案 (项目代码.控制台等) 最近IDE从eclipse改成IntelliJ IDEA 了,原因是公司大部分人都在用这个IDE,而且一直推荐用,所以尝尝鲜.换的第一 ...
- WPF外包公司——北京动点飞扬软件:开发企业WPF项目需要掌握些什么
做为企业开发一个WPF项目,对于很多不熟悉微软WPF技术和XAML语言开发团队而言,北京动点飞扬在此给各位一点建议: 1.首先开发团队要整体对于XAML和WPF的运作机制熟悉. 2.开发人员起码要会用 ...
- 嵌入式 十个最值得阅读学习的C开源项目代码
开源世界有许多优秀的开源项目,我选取其中十个最优秀的.最轻量级的C语言的项目,希望可以为C语言开发人员提供参考. 十个最值得阅读学习的C开源项目代码 1. Webbench 2. Tinyhttpd ...
- 为WPF项目创建单元测试
原文作者: 周银辉 来源: 博客园 原文地址:http://www.cnblogs.com/zhouyinhui/archive/2007/09/30/911522.html 可能你已发现一个问题, ...
- 百万行mysql数据库优化(补充)
我上大学的那个时候喜欢研究一些数据库的技术,那时候觉得数据处理很重要,而且数据优化也是相当重要的,看了很多数据库方面的资料,虽然在实际的项目也遇到过一些数据库优化的任务,完成之后还是有些心情愉快.但是 ...
- 百万行mysql数据库优化和10G大文件上传方案
百万行mysql数据库优化和10G大文件上传方案 最近这几天正在忙这个优化的方案,一直没时间耍,忙碌了一段时间终于还是拿下了这个项目?项目中不要每次都把程序上的问题,让mysql数据库来承担,它只是个 ...
- WPF项目学习.一
WPF项目搭建 版权声明:本文为博主初学经验,未经博主允许不得转载. 一.前言 记录在学习与制作WPF过程中遇到的解决方案. 使用MVVM的优点是 数据和视图分离,双向绑定,低耦合,可重用行,相对独立 ...
随机推荐
- scrapy--使用案例
1.scrapy框架 1.1 安装scrapy pip3 install wheel 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twis ...
- 学习saltstack (六)
Slatstack 介绍 官网:https://saltstack.com/ 官方源:http://repo.saltstack.com/ (介绍各操作系统安装方法) centos 6源 ? 1 y ...
- 设置python 虚拟环境 virtualenv django 虚拟环境
https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment Ubuntu vir ...
- 1、Jetson Nano 远程桌面XP问题
jeston nano上网 方法3(最简单的方法) 最简单的方法真的特简单,用USB数据线连接主板的USB接口以及手机,打开手机的USB共享即可,若要使用静态IP,可在主板上修改配置文件,接口一般为u ...
- 前端规范(ES6BEMOOCSSSMACSS)
前端规范 在实际开发中,由于团队成员编码习惯不一,技术层次不同,开发前定制并遵循一种代码规范能提高代码质量,增加开发效率. Javascript Javascript规范直接参考airbnb: ES6 ...
- JS 用状态机的思想看Generator之基本语法篇
前言 最近学习了阮一峰老师的<ECMAScript 6 入门>里的Generator相关知识,以及<你不知道的JS>中卷的异步编程部分.同时在SegmentFault问答区看到 ...
- ES6-11学习笔记--Symbol
Symbol:一种新的原始数据类型 声明方式: let s1 = Symbol() let s2 = Symbol() console.log(s1); // Symbol() console.l ...
- AS之去掉顶部标题栏
在该目录下,将原本<style name的这行代码改为: <style name="Theme.Tongxunlu" parent="Theme.Materi ...
- springMVC中获取request和response对象的几种方式(RequestContextHolder)
springMVC中获取request和response对象的几种方式 1.最简单方式:参数 2.加入监听器,然后在代码里面获取 原文链接:https://blog.csdn.net/weixin_4 ...
- 小程序拿checkbox的checked属性
方法一.checkbox <checkbox class="round red" bindtap="checkboxChange" checked=&q ...