野生架构师的狂想让C#和Java代码长得一模一样非科班野生程序员深耕政务信息化20年。自己搓了一套框架Java版跑了十几年2014年接了个某海关的C#项目干了一件狂事——把Java框架的核心设计原封不动搬到C#里让两个语言的业务代码长得一模一样。这篇文章讲的就是这个决策的前因后果。最后感谢豆包、智谱、OpenCode决策是我做的代码是我搓的文字是他们总结的。缘起某软给我的礼物在某软的那几年我是项目经理。接触到了大量 PowerBuilder 项目看到了 DataStore 的威力也看到了东软自己做的 Java 版实现。但看和干是两回事。在某软我只管进度和质量不用操心技术选型。离开某软后自己扛架构师这个角色才发现以前觉得理所当然的东西每一行都得自己写出来、跑通、扛住生产环境的压力。那年我34岁独立承担架构师才2年。自己搓 Java Web 框架browise核心就是照着 DataStore 的思路来DataStore、RowSet、Row、DataCenter。轻量版只取了最精华的那一层。这套东西在 Java 端跑了几年很顺。然后2014年某海关的项目来了。2014年某海关加工贸易监管系统这个项目没得选。海关总署的技术栈绑定了微软我们必须用 .NET——ASP.NET Web IBatisNetiBATIS 的 .NET 版 Spring.NET WinForms。不是我们选了 C#是上面定的改不了。更要命的是这个项目是用预算的一半抢下来的。不想办法省成本就赔死。团队都是写 Java 的没碰过 C#。按常规做法要么招 C# 开发加人加钱赔更多要么团队现学工期拖延也赔。我没犹豫选了第三条路把 browise 框架的核心搬到 C# 里。不是重写不是参考是平移。看看代码Java 的 Row.java// com.browise.core.util.ds.RowpublicclassRow{privateint_t0;privateHashMapString,ObjectmapnewHashMapString,Object();privateHashMap_onewHashMap();publicObjectgetItemValue(Objectkey){returnmap.get(key);}publicvoidsetItemValue(Objectkey,Objectvalue){if(map.get(key)null){if(_t0)_t1;// 标记为新增}else{if(_t0){_t3;// 标记为修改_o.put(key,map.get(key));// 保存原始值}}map.put(key.toString(),value);}publicbooleanisModefiy(){return_t3;}publicbooleanisInsert(){return_t1;}}C# 的 Row.cs// com.bluepoint.util.ds.RowpublicclassRow{privateint_t0;privateHashtablemapnewHashtable();privateHashtable_onewHashtable();publicobjectgetItemValue(objectkey){returnmap[key];}publicvoidsetItemValue(objectkey,objectvalue){if(map[key]null){if(_t0)_t1;// 标记为新增}else{if(_t0){_t3;// 标记为修改_o.Add(key,map[key]);// 保存原始值}map.Remove(key);}if(map.ContainsKey(key))map[key]value;elsemap.Add(key,value);}publicboolisModefiy(){return_t3;}publicboolisInsert(){return_t1;}}不是相似是一模一样。类名一样、方法名一样、字段名一样、状态机逻辑一样_t0无变化_t1新增_t3修改连_o存原始值的设计都一字不差。再看 DataStore// JavaDataStoredsnewDataStore(table);Rowrowds.getRowset().add();row.setItemValue(name,张三);row.setItemValue(age,34);Stringjsonds.toJson();// C#DataStoredsnewDataStore(table);Rowrowds.getRowset().add();row.setItemValue(name,张三);row.setItemValue(age,34);Stringjsonds.toJson();两段代码除了文件后缀名看不出区别。完整搬了什么不只是 DataStore。整个核心层都搬了Java (com.browise.core)C# (com.bluepoint)说明DataCenterDataCenter顶层容器HeaderHeader消息头BodyBody消息体DataStoreDataStore数据集RowSetRowSet行集primary/delete/filter三区RowRow行状态机追踪变更DaoDao基类ResultToMap/MapToDaoListPropertyListProperty反射工具AppContextAppContext上下文接口AppContextContainerAppContextContainer线程存储AppContextImplAppContextImpl上下文实现DBUtilSqlMapperExtension数据库工具IBatisNet封装连 WebService 的统一入口都一样——Java 版是 Servlet 路由BusinessActionC# 版是interface.asmx的BusinessAction都是接收DataCenter的 JSON反射调用业务方法返回DataCenter的 JSON。前后端协议也统一了——前端不管跟 Java 后端还是 C# 后端通信发的都是 DataCenter JSON收的也是 DataCenter JSON。代价当然有代价。C# 有 Property 语法本应该写成row.Name 张三我硬是用 Java 的 getter/setter 风格row.setItemValue(name, 张三)。C# 有 LINQ有泛型协变逆变有var关键字——这些语言优势全放弃了。C# 的 Hashtable 不是泛型的有装箱拆箱开销我也认了。因为统一性比语言惯用性更重要。为什么敢这么干三个原因。第一团队。政务项目人力不稳定一个开发可能上半年在 Java 项目组下半年调到 C# 项目组。框架统一了调岗不用培训。getItemValue/setItemValue/getRowset/isInsert/isModefiy会一套就全都会。降低的不是学习成本是项目的人力风险。第二我已经验证过了。Java 版的 DataStore 已经跑了好几年各种边界情况都踩过坑。搬到 C# 不是试错是复制一个已经被证明可行的设计。风险可控。第三C# 不争气。2014年.NET Framework 只能跑 Windows。语言设计比 Java 优雅得多但生态封闭、部署受限。政务项目大量 Linux 服务器Java 不可替代。既然两个语言都要用不如让它们共用同一套编程模型减少心智负担。某海关项目落地了这不是 PPT。某海关加工贸易监管系统覆盖了完整的业务链条账册管理经营账册、贸易账册、简易加工账册的备案、审批、变更报关单管理报关单录入、清单管理、草稿箱仓储管理仓库、库位、泊位、地磅、出入库数据报核与海关 H2000 系统对接MSMQ 消息传输大文件分片、报文生成、回执处理C# 版框架 27 个业务模块54 个 SQL 映射文件ASP.NET Web 前端 WebService WinForms MQ 接收端。实打实的生产系统。有人说这是反模式确实。正统观点是每种语言应该用自己的惯用写法。C# 就该有 C# 的样子Java 就该有 Java 的样子。我的做法违反了这个原则。但政务信息化有个特点人力成本远大于技术成本。一个程序员离职新人接手要两周。如果框架不同接手时间翻倍。十个项目、二十个开发、每年流动三四个人——这个成本算下来远比代码不够地道严重得多。而且说到底DataStore 这套设计本身就跟语言无关。它是 PB 时代的产物我用 Java 实现再用 C# 实现再用 JavaScript 在前端实现。它不是 Java 的也不是 C# 的它是数据的。十几年后的回看Java 版从最初的核心 ds 包几个类发展到现在 120 多个类、14 个模块——IoC 容器、AOP 代理、拦截器链、MyBatis 深度定制、工作流引擎、MongoDB 混合存储……但底座始终是那一套 DataStore。C# 版因为项目下线了停留在 2014 年的 23 个类。但那套设计没有下线——它活在 Java 版里继续迭代。如果当年没有跨语言统一这个决策某海关项目要么招 C# 开发增加人力成本要么团队现学 C#增加时间成本要么我用 C# 惯用方式重写一套增加维护成本。三条路都比直接平移贵。有时候最好的架构决策不是选择什么技术而是消除选择的必要。你见过跨语言统一编程模型的做法吗评论区聊聊。标签#架构设计 #CSharp #Java #DataStore #跨语言 #政务信息化 #自研框架 #PowerBuilder #编程模型统一