and*_*soj 13 java refactoring class data-objects
重构大型"仅状态"对象有哪些常见策略?
我正在研究一个特定的软实时决策支持系统,该系统可以对国家空域进行在线建模/模拟.该软件消耗大量实时数据馈送,并对空域中的大量实体的"状态"进行每分钟一次的估计.这个问题整齐地分解,直到我们达到目前最低级别的实体.
我们的数学模型估计/预测每个这些实体的过去和未来几个小时的时间线的50个参数,大约每分钟一次.目前,这些记录被编码为具有大量字段的单个Java类(一些字段被折叠成一个ArrayList).我们的模型正在不断发展,并且各个领域之间的依赖关系还没有一成不变,因此每个实例都会在一个复杂的模型中漫游,随着它的发展积累设置.
目前我们有类似下面的内容,它使用构建器模式方法来构建记录的内容,并强制执行已知的依赖项(作为程序员错误的检查,演变模式.)一旦估计完成,我们使用.build()类型方法将以下内容转换为不可变形式.
final class OneMinuteEstimate {
enum EstimateState { INFANT, HEADER, INDEPENDENT, ... };
EstimateState state = EstimateState.INFANT;
// "header" stuff
DateTime estimatedAtTime = null;
DateTime stamp = null;
EntityId id = null;
// independent fields
int status1 = -1;
...
// dependent/complex fields...
... goes on for 40+ more fields...
void setHeaderFields(...)
{
if (!EstimateState.INFANT.equals(state)) {
throw new IllegalStateException("Must be in INFANT state to set header");
}
...
}
}
Run Code Online (Sandbox Code Playgroud)
一旦完成了大量这些估算,就会将它们汇总到时间线中,分析汇总的模式/趋势.我们已经考虑过使用嵌入式数据库,但却遇到了性能问题.我们宁愿在数据建模方面对其进行整理,然后将部分软实时代码逐步移动到嵌入式数据存储中.
完成"时间敏感"部分后,产品将刷新为平面文件和数据库.
问题:
build()方法非常麻烦.开箱即用的想法,但这是我们需要逐步发展的东西.在其他人说出来之前,我会注意到,如果该模型的数据表示很难得到,我们的数学模型可能会表明我们的数学模型不够清晰.公平的,我们正在努力,但我认为这是一个研发环境的副作用,有很多贡献者,并且有很多并发的假设在起作用.
(并不重要,但这是用Java实现的.我们使用HSQLDB或Postgres作为输出产品.我们不使用任何持久性框架,部分原因是缺乏熟悉性,部分原因是我们只有数据库有足够的性能问题单独和手工编码的存储例程......我们对进一步的抽象持怀疑态度.)
我遇到了很多同样的问题.
至少我想我做过,听起来像我做的那样.表示方式不同,但在10000英尺处,声音几乎相同.离散的"任意"变量和它们之间的一系列临时关系(基本上是业务驱动的)的大量负载,可能会在瞬间发生变化.
您还有另一个问题,您可以提及,这就是性能要求.听起来更快更好,并且可能是一个缓慢完美的解决方案将被抛到快速糟糕的一个,只是因为较慢的一个不能满足基线性能要求,无论它有多好.
简单地说,我所做的是为我的系统设计了一个简单的特定领域规则语言.
DSL的全部意义在于隐含地表达关系并将它们打包到模块中.
非常粗糙,人为的例子:
D = 7
C = A + B
B = A / 5
A = 10
RULE 1: IF (C < 10) ALERT "C is less than 10"
RULE 2: IF (C > 5) ALERT "C is greater than 5"
RULE 3: IF (D > 10) ALERT "D is greater than 10"
MODULE 1: RULE 1
MODULE 2: RULE 3
MODULE 3: RULE 1, RULE 2
Run Code Online (Sandbox Code Playgroud)
首先,这不代表我的语法.
但是你可以从模块中看到它是3个简单的规则.
关键是,由此可见,规则1取决于C,取决于A和B,B取决于A.这些关系是隐含的.
因此,对于该模块,所有这些依赖关系"随之而来".你可以看看我是否为模块1生成了它可能看起来像这样的代码:
public void module_1() {
int a = 10;
int b = a / 5;
int c = a + b;
if (c < 10) {
alert("C is less than 10");
}
}
Run Code Online (Sandbox Code Playgroud)
如果我创建了第2单元,那么我将得到的是:
public void module_2() {
int d = 7;
if (d > 10) {
alert("D is greater than 10.");
}
}
Run Code Online (Sandbox Code Playgroud)
在第3单元中,您会看到"免费"重用:
public void module_3() {
int a = 10;
int b = a / 5;
int c = a + b;
if (c < 10) {
alert("C is less than 10");
}
if (c > 5) {
alert("C is greater than 5");
}
}
Run Code Online (Sandbox Code Playgroud)
因此,即使我有一个"汤"规则,模块根据依赖关系的基础,从而过滤掉它不关心的东西.抓住一个模块,摇动树木并保持悬挂的东西.
我的系统使用DSL生成源代码,但您也可以轻松地创建一个迷你运行时解释器.
简单的拓扑排序为我处理了依赖图.
因此,关于这一点的好处在于,虽然最终生成的逻辑中存在不可避免的重复,至少在模块之间,但规则库中没有任何重复.您作为开发人员/知识工作者所维护的是规则库.
同样好的是你可以改变一个方程式,而不用担心副作用.例如,如果我改变做C = A/2,那么,突然,B完全退出.但IF(C <10)的规则根本没有变化.
使用一些简单的工具,您可以显示整个依赖图,您可以找到孤立的变量(如B)等.
通过生成源代码,它将以您想要的速度运行.
在我的例子中,有趣的是看到规则删除单个变量并且看到500行源代码从结果模块中消失.这是500行,我不需要手动爬行并在维护和开发过程中删除.我所要做的就是在规则库中更改一条规则,让"魔法"发生.
我甚至可以做一些简单的窥视孔优化并消除变量.
这并不难.您的规则语言可以是XML,也可以是简单的表达式解析器.如果你不想,没有理由去Yacc或ANTLR.我将为S-Expressions插入一个插件,不需要语法,脑死解析.
实际上,电子表格也是一个很好的输入工具.只是严格格式化.在SVN中合并很糟糕(所以,不要这样做),但最终用户喜欢它.
您可能能够使用基于规则的实际系统.我的系统在运行时并不是动态的,并且不需要复杂的目标搜索和推理,因此我不需要这种系统的开销.但如果一个人开箱即用,那么快乐的一天.
哦,对于一个实现说明,对于那些不相信你可以在Java方法中达到64K代码限制的人,我可以向你保证可以做到:).