OO 第四单元总结暨课程总结:Simplest is the Best
第四单元架构设计
经过一定分析后发现暴力查询的复杂度在目前的时限下可以接受,故采用暴力的做法,全程随问随查。
整体架构是裸的数据集。查询时直接将之流化,在流中筛选。
以 getInformationNotHidden
为例:
@Override
public List<AttributeClassInformation> getInformationNotHidden(String s)
throws ClassNotFoundException, ClassDuplicatedException {
return findFathers((UmlClass) getClass(s)) // 首先获取所有继承的类
.stream()
.map(c -> // 对每个父类:
select(ElementType.UML_ATTRIBUTE)
// 取得 attr
.filter(u -> u.getParentId().equals(c.getId()))
// 筛选可见性
.filter(u -> ((UmlAttribute) u).getVisibility() != Visibility.PRIVATE)
// 映射成返回数据类型
.map(u -> new AttributeClassInformation(u.getName(), c.getName()))
)
.reduce(Stream::concat) // 整合、转换
.orElse(Stream.empty())
.collect(Collectors.toList());
}
再举一例:
@Override public void checkForUml008() throws UmlRule008Exception {
List<UmlElement> rg = select(ElementType.UML_PSEUDOSTATE).collect(Collectors.toList());
if (!rg.stream().map(u -> {
List<UmlTransition> ret = select(ElementType.UML_TRANSITION)
.map(o -> (UmlTransition) o)
.filter(o -> o.getSource().equals(u.getId()))
.collect(Collectors.toList()); // 所有从这个初态向外的 trans
return ret.isEmpty() || ret.size() == 1 && ret.get(0).getGuard() == null
&& select(ElementType.UML_EVENT)
.noneMatch(o -> o.getParentId().equals(ret.get(0).getId()));
}).reduce(Boolean::logicalAnd).orElse(true)) {
throw new UmlRule008Exception();
}
}
好处是信息的来龙去脉清晰,逻辑显化。
select(ElementType)
的功能类似 $('el')
。
当时还想过要不要做 $('#el > el')
(按 Id/ParentId
筛选),不过后来懒得写这个相当于 querySelector 的东西了,还是用着函数式一把梭解决。
存储结构如下:
private final UmlElement[] elements; // 各元素
private final HashMap<String, UmlElement> pool = new HashMap<>(); // 查 ID 对应的元素
private static final HashSet<String> VALID = new HashSet<>(Arrays.asList( // 合法类型
"byte", "short", "int", "long", "float", "double", "char", "boolean", "String"));
// 需要有名字的 Type,用 ordinal 表示
private static final HashSet<Integer> NAMED = new HashSet<>(Arrays.asList(0, 3, 4, 5, 8));
尽管 ordinal 的可靠性不如直接使用类型本身,为什么还是用 ordinal 表示呢,主要是因为代码行数有点紧缺。
前两次作业比较精简,单文件行数限制比较松,但第三次的时候碰到了爆 500 行的问题。
于是经过精雕细琢(诶我就是不拆类出来,逻辑就嗯往一个 Interact 里塞,就是玩),终于达到了两个文件加起来刚好 500 行(7 + 493)的成就。
三次作业情况:
编号 | 行数(含空行) | 码量 | 行均字符数 | 强测成绩 |
---|---|---|---|---|
1 | 335 | 14.8KB | 45.44 | 100 |
2 | 441 | 21.3KB | 49.53 | 100 |
3 | 500 | 26.3KB | 54.00 | 100 |
架构设计及面向对象方法理解演进
实际上我个人并没有什么演进。
第一单元一开始懒得写,直接暴力;后来用的是三级架构:Factor -> Product -> Sum,然后暴力求导加上一些简单处理,就得到了一个比较不错的成绩。
第二单元刚开始一直纠结调度粒度,后来直接把调度器扬了,就嗯自由竞争运载需求,也得到了一个比较不错的成绩。
第三单元由于有 JML,所以写的时候基本没怎么动脑子,然后就错在没有 JML 的时候和懒得写复杂度好的算法的时候。
第四单元流式一把梭,数据相关逻辑在代码层面上特别清晰,唯一比较难读的是指导书。
从我个人层面来说,我个人觉得我的面向对象理解在课程里并没有演进多少。除第二单元,完成作业使用到的结构和算法都比较熟悉,接触函数式也不是从 Java 开始的,而是在 C++11/14/17/20 中便积累了一定的熟练度;第一单元的新鲜感来源于编译;第二单元的新鲜感主要来源于并发设计。这二者和面向对象这个主题,不能说没有关系,只能说不是非常具有代表性。诚然,对于从未写过 Java 的人来说,用对象表示式子、表示电梯,可能很新鲜。但是对于具有一定编程经验的学习者而言,便显得很稀松平常。相比之下,可能理论课的内容更能帮助我对面向对象思维进行准确的把握和思考。
测试理解与实践演进
在课程中我一直秉承手动构造具有代表性的测试数据的思想。
偶尔会和采用自动式测试的同学进行交流。
第四单元并未使用单元测试,原因是各函数逻辑的内聚程度非常高,逻辑非常清晰,肉眼即可保证很高程度的正确性。
不去建设自动测试的原因是认为没有这种必要:相信自己的 bug 是少数,不愿意投入大量精力去获得这样的小量的改进。OO 的边际报酬递减得很厉害。
总的来说,测试这方面也并没有什么演进。
课程收获
第一单元:知道了自己是一个没有必要/不感兴趣的情况下懒得做大量小型优化的人。
第二单元:有时候不妨做做颠覆性的改变。
第三单元:如果大家都用数理逻辑交流的话或许就是这样吧。
第四单元:提升了 Java 流式编程熟练度,知道了有 UML 这么一个概念模型格式,有 StarUML 这么一个玩意(虽然是到了上机才知道的)(也可能是因为没去上课)(虽然暂时也根本用不到)。
总的感受:理论课可能对我更有用。
课程改进建议
- 精简也是一种美。满足同样需求的代码量少或许也可以纳入评奖?
- 搞点内容创新。年年多项式求导,年年电梯,开学就有人写完了,有意思么 = =
- 晚上别关平台。或者把时间调一下。本来干活的时间就少,还得给你挪个白天,不然只能靠缓存的指导书,容易么 = =
- 把 CheckStyle 修订一下吧,都被玩坏了,例子(以下例子均不违反 CheckStyle):
@Override public List<OperationParamInformation> getClassOperationParamType(String s, String s1)
throws ClassNotFoundException, ClassDuplicatedException,
MethodWrongTypeException, MethodDuplicatedException {
UmlElement e = getClass(s);
AtomicBoolean error = new AtomicBoolean(false);
List<OperationParamInformation> rtr = select(ElementType.UML_OPERATION)
.filter(u -> u.getParentId().equals(e.getId()) && u.getName().equals(s1))
.map(u -> {
ArrayList<String> params = new ArrayList<>();
String r = null;
for (UmlElement p : elements) {
if (p.getElementType() == ElementType.UML_PARAMETER) {
if (p.getParentId().equals(u.getId())) {
UmlParameter pp = (UmlParameter) p;
String name = getParamTypeName(pp, pp.getDirection() ==
Direction.RETURN ? this::checkAllowVoid : this::checkType);
if (name == null) { error.set(true); }
else {
if (pp.getDirection() == Direction.RETURN) { r = name; }
else { params.add(name); } }
} } }
return new OperationParamInformation(params, r);
}).collect(Collectors.toList());
if (error.get()) { throw new MethodWrongTypeException(s, s1); }
else {
List<OperationParamInformation> realRet = new ArrayList<>(new HashSet<>(rtr));
if (realRet.size() == rtr.size()) { return realRet; }
else { throw new MethodDuplicatedException(s, s1); }
}
}
@Override public void checkForUml003() throws UmlRule003Exception {
final Set<UmlClassOrInterface> set = select(ElementType.UML_INTERFACE).filter(
i -> {
HashSet<UmlElement> all = new HashSet<>();
Queue<UmlElement> queue = new ArrayDeque<>(Collections.singletonList(i));
while (!queue.isEmpty()) {
UmlElement ee = queue.poll();
if (all.add(ee)) { queue.addAll(findExtended(ee)); }
else { return true; }
}
return false; }
).map(u -> (UmlInterface) u).collect(Collectors.toSet());
if (!set.isEmpty()) { throw new UmlRule003Exception(set); }
}