专栏名称: 逸言
文学与软件,诗意地想念。
目录
相关文章推荐
程序员小灰  ·  这个春节,小灰一天都没休息 ·  3 天前  
程序员的那些事  ·  o3-mini ... ·  6 天前  
程序员小灰  ·  如何用DeepSeek来变现?90%的人都不知道 ·  4 天前  
51好读  ›  专栏  ›  逸言

项目札记008 | 团队成员的能力培养

逸言  · 公众号  · 程序员  · 2024-12-27 08:30

主要观点总结

文章主要讨论了软件团队开发中代码质量的重要性以及如何提高团队成员能力。文章以一个具体项目为例,指出代码质量问题以及修复过程中遇到的困难,强调开发人员在初期就应注意代码质量,并遵循面向对象设计原则。同时,文章也提到了培养和提高研发团队成员能力的重要性,明确了技术路线下的细分方向,并强调了突破发展瓶颈的必要性。

关键观点总结

关键观点1: 代码质量问题

文章指出开发人员缺乏精益求精的精神,代码存在不必要的重复和难以理解的逻辑,使用了复杂的容器对象导致性能问题和代码可读性差的后果。同时提到修复bug时的困难,因为遗留代码使用了复杂的Map结构,涉及广泛公开的方法和类,改善需要花费大量时间和资源。

关键观点2: 代码重构的挑战

文章提到虽然认识到了代码编写的诸多问题,并想到了更符合OO原则的解决方案,但由于遗留代码使用复杂容器对象的类和方法非常多,且没有充分的单元测试,改善需要大量时间和资源,难以在短期内见效。

关键观点3: 团队成员能力培养的重要性

文章强调了培养和提高研发团队成员能力的重要性,包括明确职业目标、选择发展方向、提高自我突破能力、保持持续学习等。提出了技术人员在进入发展瓶颈期后需要寻找突破方向,否则能力将难以提升。


正文

EISaaS团队的开发人员无疑具备了快速实现软件功能的能力,但由于他们从一开始缺乏编写高质量代码的正确认识和严格训练,写出来的代码质量确实不敢恭维。
以下这段代码是当时一位开发人员的“杰作”:
public Object load(String name) { Object obj = null; Object objFromMap = map.get(name); if (objFromMap != null) { obj = objFromMap; return obj; } else { Object objFromSpring = springFunctionLoader.load(name); if (objFromSpring != null) { obj = objFromSpring; map.put(name, objFromSpring); return obj; } else { Object objFromReflect = reflectionFunctionLoader.load(name); if (objFromReflect != null) { map.put(name, objFromReflect); obj = objFromReflect; return obj; } } } return null;}
load()方法期望根据名字加载对应的对象,整个加载过程分为三个步骤:
  1. 检查缓存中是否已经存在符合条件的对象,如果有,则返回,否则执行第2步;
  2. 通过Spring框架加载符合条件的对象,如果加载成功,加入缓存并返回,否则执行第3步;
  3. 通过反射创建符合条件的对象,如果创建成功,加入缓存并返回,否则返回null

返回的null的问题暂且不说,毕竟那个时候的Java版本还未引入Optional ,开发人员应该也不知道Null Object模式。 这里的问题在于不必要的代码重复。 如果只使用一个临时变量obj,代码更简洁,也能减少不必要的临时变量,而多余的else也让代码更难理解。改进如下:
public Object load(String name) { Object obj = map.get(name); if (obj != null) { return obj; } obj = springFunctionLoader.load(name); if (obj != null) { map.put(name, obj); return obj; } obj = reflectionFunctionLoader.load(name); if (obj != null) { map.put(name, obj); } return null;}
整个方法少了7行,没有不必要的嵌套,逻辑更加清晰。类似这样的问题固然是初学者容易犯的毛病,却也是因为开发人员 缺乏精益求精的精神 ,只管实现,不考虑可读性、可重用性、可扩展性等内部质量。
我当时还未加入ThoughtWorks,对一些好的敏捷实践缺乏直观认识,也没有培养和提升团队成员能力的意识。看到一些具体问题,我会指出,却从未想过要在团队内部规定良好的编码纪律,形成良好的学习氛围,培养他们追求卓越的工匠意识。当时的我也不知道持续集成,也不曾用过代码的静态检查工具,在面对大量的代码内部质量问题,尤其是可读性、可复用性问题时,没有坚持精益求精,反而是得过且过。
记得当时公司市场部反馈过来一个要命的bug,其他开发人员都在产品开发任务上,就由我来认领缺陷修复的任务。
该缺陷来自成绩报告。需求假定客户设置分数段时,不同的分数段有不同的有效分,对应了不同的名次。这些数据都是经过分析器分析获得,并持久化到数据库中。需要生成成绩报告时,会从数据库中获取分析结果,将数据填充到iReport设置好的模板中,一个是二维表,一个是柱状和曲线图。
现在发现某些学校需要给不同的分数段设置完全相同的有效分以及相同的名次。报告打印出来,二维表没有错,曲线图却出现了“缺斤少两”的现象。例如设置五个分数段,却可能只显示了四条曲线。
通过阅读代码,我明白了原因。在实现中,由于默认不同分数段有不同名次,因此,在获取这些分数段的值时,将它们放入一个 Map > 中。这个Map是根据科目进行分类的,子Map的key值为Integer类型,为分数段对应的名次,value则是设置的有效分。现在,因为作为key的名次出现重复,就会“吞”掉对应的有效分,导致Map的元素存在偏差。这就是五个分数段只显示四条曲线的缘由。
且不说Bug的修复,单说这样的代码实现就够人头疼的,因为用到了类似 Map > 这般的“超级容器”。 这样的超级容器往往成为坏代码的泥沼。 将这样的对象作为参数,在方法之间传来传去时,会带来诸多问题:
  • 性能影响:这样庞大的容器对象,可能形成性能瓶颈;
  • 强类型:虽然这里使用了泛型,但泛型类型却使用了基本类型;
  • 封装性不够好:容器对象暴露了太多的数据细节,且不利于为其定义职责行为。
  • 可读性差:看到这样的Map,你并不会在第一时间明白它到底存放了什么。
  • 可扩展性差:当这个Map作为方法的参数时,相当于这个参数没有被对象化。如果拥有这个参数的方法被公开,且广泛调用,一旦需要改变参数,牵连到的代码就会非常多。
事实正是如此。当我在分析产品的遗留代码时,发现很多地方都在重复获取这个Map对象,该Map对象也在多个方法之间传递。例如这样的代码:
public static JFreeChart createEliteTotalChart(Grade grade, String partial, ExamSet es, Student stu, Map subToStuTotalMap, MapMap> subToValidScoreMap, List subList,long stuRank, String[] validLineSeries,double barWidth,Color barColor) { if (subToStuTotalMap == null || subList == null) { return null; }
Color[] color = {Color.RED,Color.BLUE,Color.GREEN, Color.CYAN,Color.BLACK,Color.MAGENTA};
CategoryDataset[] dataset = EIDatasetFactory.createEliteTotalDataset( subToStuTotalMap, subToValidScoreMap, subList,validLineSeries);
//......为清晰起见,其余代码略}
注意,在 createEliteTotalChart() 方法中,调用了 EIDatasetFactory createEliteTotalDataset() ,对 Map > 对象进行了处理。 EIDatasetFactory 正是一个公开的公共类, createEliteTotalDataset() 方法也是一个被广泛调用的方法。
因为这种容器对象自身的缺陷,为我的bug修复带来了很多阻碍。要解决这个Bug,就不能再将名次作为Map的key值。查看相关的数据表,事实上我们还给出了一个分数段的名称。当名次和有效分存在重复的情况下,结合分数段名称就能确定唯一值。一个简单的做法就是将Map的key修改为“名次加名称”的组合,即将Integer修改为String。
另一种做法则是运用对象的封装来实现这一目标,如定义ValidScore和ValidScoreRange。ValidScore定义了属性:Rank、LineName和Score,而作为ValidScore集合的ValidScoreRange,可以完全替换之前的 Map 。一旦定义了这两个对象,就可以将与之相关的领域行为分配给它们,职责就会变得更加集中,避免了相同逻辑的代码蔓延。
虽然当初我认识到了代码编写的诸多问题,也想到了更加符合OO原则的解决方案,并能够利用重构来完成代码的改造。但是,我却下不了这个决心。
当前,我的最高优先级是修复Bug,用最简单的解决方案,几分钟就可以完成,且可以保证不会引入新的Bug,这虽然是得过且过的做法,却是目前最经济最高效的选择。倘若在这时要追求代码质量的精益求精,成本就太大了。






请到「今天看啥」查看全文