百万行WPF项目代码重构记录分析

目录
  • 一 产品型号太多产生非常多重复性工作
  • 二 配置零散,没有统一的管理机制,不利于打包
  • 三 UI和业务逻辑混杂
  • 四 开发人员的层次不一
  • 五 处理大量的图片 内存和CPU占用过大

此前带领小组成员主导过一个百万行代码上位机项目的重构工作,分析项目中存在的问题做了些针对性的优化,整个重构工作持续了一年半之久。

主要针对以下问题:

一 产品型号太多产生非常多重复性工作

产品型号太多导致代码工程的分支太多,维护时会产生非常多的重复性的工作。

这是一个历史遗留问题,公司成立之初的开发人员在开发时没有考虑到后期其他机型的合并而留有余地,后面增加其他机型而导致的代码差异,是直接通过创建工程的分支来进行维护。两个不同机型之间可能大部分的业务逻辑都相同而只有少部分的界面和业务存在差异性,当修改一个共有的bug时,需要在所有分支上面都修改一遍。

同时有可能因为分支代码的改动,而无法直接进行代码合并,进而导致重复工作量的增加。如果分支不多的时候倒还好,而当机型越来越多,分支越来越多,这个重复性的工作会消耗相当大的时间和精力。所以合并所有机型的代码,实现上位机代码在不同机型上的通用性势在必行。

一个良好的软件架构应当是 面向接口编程,而不是面向实现编程。对于不同机型中的业务和流程在主体上是一样的,只是其中某些细节存在差异性的分支,所以我们需要将业务代码提炼出主体流程和对应的接口,通过这些业务接口来实现机型差异所带来的的业务流程上的差异和分支,而非工程上的分支。

在软件运行时,可以通过相应的配置来决定业务接口的具体实现是哪一个,同时由于不同机型的接口实现是分离的,修改一个机型不会影响到另一个机型的代码。当然,在修改主流程代码时候就要小心了,需要考虑这一个改动对所有机型的影响。除非是非常有把握的情况下直接改动,否则还是先抽象出接口保证原主流程代码不变,只修改你需要修改的实现。

二 配置零散,没有统一的管理机制,不利于打包

软件中的配置数据保存的地方太零散,有保存在数据库的,有保存在txt文件的,有保存在注册表的,有保存在app.config的。经过不同开发人员的不断迭代,积累了很多无用的配置数据,并且没有人敢删除。而售后有时候为了查找修改某个配置需要在各个地方查找,非常繁琐。

同时也因为公司机型太多导致很多配置数据在不同机型上存在差异性,这些差异性有可能是来自硬件差异,也有可能是来自软件功能上的差异。而每发布一个版本,就有可能需要同时打包多个机型的软件包,每一个软件包至少有1个G 的大小。而随着机型的越来越多,一个版本不同软件包也会越来越多,这对于打包人员来说是个不小的负担,同时也要求打包人员需要明确的知道每一个机型配置上的差异性来保证软件包的正确性。

为了解决这个问题,我们花了数个月的时间,对所有机型保存在不同位置的配置做了一个整理。我们整理出了所有机型通用的配置,统一保存在数据库表中,同时为每一个机型建立数据库表用来保存存在差异的配置数据。为了方便打包人员和售后管理查看这些数据库,我们开发了一个配置管理工具用来专门查看和修改配置。

另外,我们为了解决多个软件包的问题,把所有硬件相关的配置整合进一个文件中,并开发出一个版本升级软件。在这个版本升级软件中,售后可以选择机型对应的硬件,升级程序可以通过所选硬件对应的配置写入到数据库中来实现同一个软件包不同机型的升级工作。

同时我们开发了通用的http接口给上层C#程序和下层C++程序使用,用于读写数据库的配置数据。

由于我们的设备有多个PC,并且在医院内部无法连接外网,此前软件升级时每个PC都需要售后人员拷贝软件包并手动调用升级脚本来完成升级。而现在,我们的升级程序可以通过远程调用的方式来同时完成多个PC的升级工作,做到了一键升级功能。

基于第一点的软件代码合并,在本次配置优化之后,打包人员每次打包仅需要一个软件包,即可实现不同机型的一键升级,省时又省力。

三 UI和业务逻辑混杂

项目以WPF为主,整体使用MVVM框架。项目中没有使用开源的控件库,其中含有非常多的高度自定义控件的开发,这些控件的UI表现代码和业务逻辑代码夹杂在一起,耦合性太高非常不利于理解业务代码和后期维护。

WPF的MVVM框架本身最大的优势就是为了分离业务和UI,降低耦合性,提高可重用性,所以这个项目并没有发挥出MVVM 框架的优势。针对这个问题,我们分离UI和业务,提炼出一个单纯的UI库,这个UI库不包含任何的业务代码,除了.Net Framework的依赖库之外,不依赖项目中的任何其他库。

每一个UI控件暴露自定义的依赖属性,在业务逻辑调用时,通过绑定这些依赖属性来改变UI的表现逻辑。这样做的好处是完全分离了UI表现代码和业务逻辑代码,并且这个UI库具有高重用性,可以交给其他项目使用,甚至可以直接开源出来。

而且在后期维护时,UI代码的改动与业务代码的改动互不干扰,也有利于Bug的排查。

四 开发人员的层次不一

由于前期开发人员的层次不一,并且没有CodeReview机制来保证代码质量,导致代码存在很多低级错误。

项目中存在大量重复代码,明明可以提炼出一个简单方便的方法,偏偏要在各个地方不断的Copy相同的代码。

明明只要增加一个参数就可以合并成一个方法,偏偏要写上几十个方法,诸如xxxx1,xxxx2....xxxx30,三十几个方法执行同样的功能,只有一个参数上的差异。我们的业绩考核又不是看代码量,这种代码看了让人啼笑皆非。

没有统一的命名规则,有些属性首字母小写,C#和C++风格混用,有些命名直接使用缩写,类似“ggr”这样缩写,除了作者谁能看懂这是什么意思?

一个类,一个方法代码过多,几千行一个类的文件不在少数,一个类承担的功能也过多也过余复杂。类和方法都应该遵循职责单一原则,不过在实际开发过程中单一原则不太好把控,但应该合理的控制代码行数。

我们在项目重构工作之前,制定了统一的命名风格,并严格限制了每个类每个方法的代码行数。一个文件,一个类,最多不超过一千行,一个方法最多不超过六十行。

六十行代码一个屏幕很难放下,这个要求其实比较低了,最合理的应该是三四十行,一个屏幕正好可以看完一个方法的所有代码。

在项目重构的过程中,我们规定所有人提交的代码都必须提交给高级工程师CodeReview,高级工程师之间互相CodeReview。每个人的知识面都是有限的,但可以通过合作来达到无限的广度和深度,我们应该尽量去避免犯一些低级的、显而易见的错误。

五 处理大量的图片 内存和CPU占用过大

5.软件需要显示处理大量的图片,导致程序内存和CPU占用过大。

由于我们的软件需要处理显示大量的Dicom文件,并且对这些展示的图片都有较为复杂的操作,诸如旋转,放大、像素提取、锐化等,这些功能都集成在一个ImageControl中。然而由于我们的ImageControl写的不合理,读取一张500K的Dicom并显示会至少占用2M的内存。如果同时读取上百个Dicom文件,程序内存可以轻轻松松突破1个G,同时由于我们系统中不止一个程序需要处理这些图片,所以对系统的内存要求非常高。

在前期,我们只能不断的累加硬件,把内存扩展到了32G,甚至是64G,然而这只是饮鸩止渴的错误方式,应该从根本上解决ImageControl控件占用内存过大的问题,同时应该优化程序显示排列图片的逻辑。

在考虑到项目人员的分配情况和项目计划,我们决定优化ImageControl这个思路暂缓,因为这个工作量会更大。我们决定先着手优化程序展示图片的逻辑。程序只加载用户能够看到的Dicom,当用户下拉进度条时,再陆续加载可见的图片,并且将其余不可见的ImageControl销毁,优化之后程序占用的内存锐减60%-70%,可以接受。

以上就是一次百万行WPF项目代码的重构记录的详细内容,更多关于WPF项目代码重构的资料请关注我们其它相关文章!

(0)

相关推荐

  • 如何搭建新的WPF项目框架

    下面就WPF项目框架搭建步骤一步一步的分享给大家. 在WPF项目开发中最常用的开发模式无疑是MVVM模式,  MVVM模式开发的好处,在这里就不详细讨论, 还有 本文中所使用MVVMLight框架,为什么使用MVVM框架(1.框架较轻,2.学习成本低.3.适用大多数中小型项目,4.相对于微软的prism框架更容易上手)    下面开始 一步一步 搭建框架 第一步: 利用反射创建VM构造器 public class ViewModelFactory { private static Diction

  • WCF如何使用动态代理精简代码架构

    使用Castle.Core.dll实现,核心代码是使用Castle.DynamicProxy.ProxyGenerator类的CreateInterfaceProxyWithoutTarget方法动态创建代理对象 NuGet上面Castle.Core的下载量1.78亿之多 一.重构前的项目代码 重构前的项目代码共7层代码,其中WCF服务端3层,WCF接口层1层,客户端3层,共7层 1.服务端WCF服务层SunCreate.InfoPlatform.Server.Service 2.服务端数据库访

  • WPF实现文本描边+外发光效果的示例代码

    解决思路: (1)描边效果可以将文本字符串用GDI+生成Bitmap,然后转成BitmapImage,再用WPF的Image控件显示. (2)外发光效果用WPF自带的Effect实现 代码: using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Text; using System.IO; namespace TextHighLighthDemo { public clas

  • WPF自动隐藏的消息框的实例代码

    (鼠标放上去将一直显示,移开动画继续),提供normal和error两种边框. 介绍:传统的确定,取消,OK,CANCAL之类的对话框太繁琐了,由于项目需要而诞生的仿手机式提示对话框.当然传统的对话框项目中也有,这里就不做介绍了. 出场和退场动画做得很简单,就用Blend随便鼓捣了一番,将就用吧. 预览效果如下: 思路其实很简单:将窗体透明化->布局和样式设计->后台传值调用. 准备工作:Microsoft.Expression.Interactions.dll和System.Windows.

  • WPF自定义搜索框代码分享

    首先下载搜索图标: 控件中的搜索图标下载地址:http://www.easyicon.net/1183666-Search_icon.html 搜索框设计过程比较简单: 1.先定义一个Rectangle作为背景 2.然后中间放TextBox输入,可以重写其中的模板.提示语Label放在模板中,可以在模板的触发器中控制隐藏显示~ 3.搜索按钮-大家随便在网上下个就行了. UserControl界面: <UserControl x:Class="WpfApplication18.SearchC

  • 百万行WPF项目代码重构记录分析

    目录 一 产品型号太多产生非常多重复性工作 二 配置零散,没有统一的管理机制,不利于打包 三 UI和业务逻辑混杂 四 开发人员的层次不一 五 处理大量的图片 内存和CPU占用过大 此前带领小组成员主导过一个百万行代码上位机项目的重构工作,分析项目中存在的问题做了些针对性的优化,整个重构工作持续了一年半之久. 主要针对以下问题: 一 产品型号太多产生非常多重复性工作 产品型号太多导致代码工程的分支太多,维护时会产生非常多的重复性的工作. 这是一个历史遗留问题,公司成立之初的开发人员在开发时没有考虑

  • 详解vue移动端项目代码拆分记录

    撸一套vue多端共用,非常适合需要快速且全面上线的项目.但是多端共用一套vue代码,由于平台间的互相限制,每端在某些业务例如支付分享等是完全独立的代码,每个平台的支付方式也会有所差异,造成在这些业务的实现过程中会有太冗余的"if else"判断.所以为了提高代码的复用性.扩展性,可以将代码拆分,以-小程序和App两端举例,一份部署到小程序,一份部署到App(Android&Ios). 首先代码拆分应该保证本地开发的时候只有一套代码,提取所有公共页面,并且分别提取小程序和app的

  • WPF项目在设计界面调用后台代码

    一.简介 如下面代码所示,在WPF项目的设计界面可以通过<x:Code> <![CDATA[  //write your code ]]></x:Code>节点,可添加后台逻辑执行的代码和方法,实现方法的调用和执行. 二.代码 WPF设计部分代码: <Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xa

  • PHP代码重构方法漫谈

    本文实例分析了PHP代码重构方法.分享给大家供大家参考,具体如下: 随着 PHP 从一种简单的脚本语言转变为一种成熟的编程语言,一个典型的 PHP 应用程序的代码库的复杂性也随之增大.为了控制对这些应用程序的支持和维护,我们可以使用各种测试工具来自动化该流程.其中一种是单元测试,它允许您直接测试所编写代码的正确性.然而,通常遗留代码库是不适合进行这种测试的.本文将介绍对包含常见问题的 PHP 代码的重构策略,以便简化使用流行的单元测试工具进行测试的过程,同时减少改进代码库的依赖性. 简介 回顾

  • 五款PHP代码重构工具推荐

    在软件工程学里,重构代码一词通常是指在不改变代码的外部行为情况下而修改源代码.软件重构需要借助工具完成,而重构工具能够修改代码同时修改所有引用该代码的地方.本文收集了五款出色的PHP代码重构工具,以帮助你完善更加优秀的项目. 1. Rephactor Rephactor是一款命令行重构工具,这是一款自动化工具,允许开发者以一种简洁的方式在不同的代码库中修改源码. 主要功能: 保证重构的可逆性-- 一旦发现问题,代码是可逆的,可以回溯到前一个版本. 查找替换功能-- 普通查找替换,方法重命名,类重

  • Nuxt 项目性能优化调研分析

    性能优化,这是面试中经常会聊到的话题.我觉得性能优化应该因具体场景而异,因不同项目而异,不同的手段不同的方案并不一定适合所有项目,当然这其中不乏一些普适的方案,比如耳熟能详的文件压缩,文件缓存,CDN,DNS 预解析,等等,但是我更希望听到的是因为不同的项目不同的需求,解决不同的问题而采取的不同的优化手段,比如 BigPipe,分段输出页面的各个部分,对于 SNS 网站是非常合适的,减少了用户的等待时间:相对应的还有一个 BigRender,这是一个大的延迟加载,360 导航首页目前还在使用,京

  • vue 项目代码拆分的方案

    背景 由于之前的数据库防火墙产品与数据库审计产品使用的是同一套代码,随着两个产品功能的差异越来越大,代码的冗余度和偶合度越来越高,为了便于后期维护以及添加新功能,所以基于原来的项目代码,进行了代码结构拆分. 注意:本次拆分只拆分了可以拆分的部分,有的模块例如:规则.关于我们,是没有进行拆分的,一是有的模块很简单,没必要拆分:二是有的模块原先写得代码偶合太严重,无法拆分,如果要拆分,需要花费大量精力去梳理代码,同时还要后端配合拆分. 目的 将此次代码拆分方案记录下来,便于后来的开发人员快速熟悉项目

  • j2Cache线上异常排查问题解决记录分析

    目录 问题背景 问题分析 假设问题 小心求证 问题重现 问题解决 问题后记-下面才是真正的原因 重新假设 最终解决 问题背景 开发反馈,线上有个服务在运行一段时间后,就会抛异常导致redis缓存不可用.项目使用了j2Caceh,异常是j2Cache的RedisCacheProvider抛出来的,如: Exception in thread "main" redis.clients.jedis.exceptions.JedisException: Could not get a reso

  • web.xml中Maven占位符不生效问题记录分析

    目录 问题背景 问题分析 Resources插件有三个目标: 问题定位 问题解决 问题背景 开发反馈,一个spring mvc的web项目,在web.xml配置的占位符不生效,编译后还是没有替换成配置的属性,如下: <context-param> <param-name>logbackConfigLocation</param-name> <param-value>classpath:${loagback.xml.path:logback.xml}</

随机推荐