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); }
}