Company Ghost Story 公司鬼故事 16

新人面談

找人來面試,如果像某些公司只有聊天,恐怕會忽略下面的狀況。

根據碩士研究的主題進行用人判定

  • 研究 GPU 的人不懂圖像渲染模型
  • 研究資安的人不懂 AES
  • 研究編碼壓縮的人不懂進制轉換
  • 研究機器學習的人不懂線性變換

不排除公司薪水福利差。即便是台清交成的學生,也只能找來有限度的研究能力人才。

研究報告

  • 報告描述沒有單位
    • 記憶體單位是什麼?MB?GB?
    • 時間單位是什麼?秒?毫秒?
  • 比較結果沒有參考對象
    • 相較於哪一個版本的?版本號是多少?
    • 使用哪一個環境的?32 位元?64 位元?
  • 文章標題沒有結構性 H1, H2, H3, …
  • 先描述結果,但定義放最後面。
  • 實驗組合結果,項目描述只有數據單位。

不排除公司薪水福利差。即便是台清交成的學生,論文可能都是教授親自寫的、實驗教授做的。

基礎學問

1
2
// pow(2, x) = y
Given y, find a upper integer x, satisfy pow(2, x) <= y.

拿到兩個自然對數的實作 (log(y)/log(2))。滿心好奇卻得到一個說法「浮點數除法沒有誤差」。那多問一句「取自然對數沒有誤差嗎?」我內心浮上答案驗證了事實「沒有」這個要牽扯說學校沒教嗎?某方面來說,道理過不去。

雜湊

雜湊任何操作都是常數 $\mathcal{O}(1)$

  1. 知道什麼是攤銷複雜度嗎?$\text{amortized} \; \mathcal{O}(1)$
  2. 拿到第一個元素是 $\mathcal{O}(1)$
1
2
3
4
HashSet<Integer> hash = new HashSet<>(input);
while (!hash.isEmpty()) {
hash.remove(hash.iterator().next());
}

這都不好說。實作方式千奇百種,上述看起來簡單,在 Java 裡面卻是 $\mathcal{O}(n^2)$

Read More +

Company Ghost Story 公司鬼故事 15

Redundant Debug Message

1
2
3
class Placement {
Image image = new BufferedImage(1024, 768); // unused
}

Naming

1
2
class Colorizer { setDrawDevice(Device), removeDrawDevice(Device)}
// setDisplay(Device, boolean)

Inheritance

1
2
class XXXPanel { Panel getPanel(); }
// why not extends Panel

String Equal

1
2
if (input.contains("yes"))
// ???, equals???

Key in Condition

1
2
3
4
5
6
7
void drawTick(Color axisColor) {
if (color.equals(Color.RED))
dx++;
else
dy++;
}
// Color as key? why not pass enum Axis

Out-of-Knowledge in Library

1
2
3
4
class AbstractTableViewRenderer {
EDA_DIE_ONLY_RENDER dieRender;
}
// don't put your shit on UI library

Over-overloading for Lambda

1
2
3
4
5
6
class AbstractTableView {
public void with(MouseInterface)
public void with(TextInterface)
public void with(IconInterface)
}
// overloading is not for heterogeneous

Oversize

1
2
long degree; // [0, 360)
// oooooooooooooover-size

Missing Else

1
2
3
4
if (geom instanceof Circle) { renderAsBall((Circle) geom); }
else if (geom instanceof Oblong) { if (type == ball) renderAsCylinder(geom);} // new feature
else renderAsPoly(geom.toPoly());
// fuckup, default as polygon, where is your else
1
2
3
4
setProperty(Object dbo, String name, Object val) {
if ("group".equals(name)) Group.set(dbo, val);
}
// where is your else and exception

OK, then Action

1
2
3
4
buttonOK.setOnAction(e -> apply());
void apply() {...; rebuildModel();}
slider.setOnChange(e -> rebuildModel()); // new feature
// our performance is gone by you

Listener

1
2
3
4
void setDisplay(...) {
fireProperytChange("SetDisplay");
}
// ... property is display, not the method name

Time-consuming Message

1
2
Object.requireNotNull(number, "The number is not illegal by " format + " with" + ... context);
// don't compose the error message, construct it on demand.

Time-consuming Render

1
2
3
4
5
class ListRenderer {
void render(ListItem e) {
ImageIO.read(getImagePath(e.getValue().getType()));
}
}

Crazy UI Design

1
2
3
4
5
6
7
|------------------|
| Chooser |
+------------------+
| [ComboBox] | # 10^6 objects
| [Cancel] [OK] |
+------------------+
# tell me, how do you pick
Read More +

Company Ghost Story 公司鬼故事 14

Java

Format vs. Concatenation

1
String.format("format " + a + "%s", b);

大部分情況,很少去生成 format string,
混合使用很容易遇到表示錯誤,因為當 a 中出現 % 等特殊字元時,解析上會發生錯誤。最好是統一一種使用規則。

Comparator Signum

1
2
3
4
5
long a = func(x);
long b = func(y);
long dir = mDir;
return (int) a - (int) b; // SHOULD Long.compare(a, b);
return (int) (a - b) * dir; // SHOULD Long.compare(a, b) * Long.signum(dir);

隨意 casting 三不五時就會 arithmetic overflow 搞砸了排序。

What’s Not-Equal

1
2
3
4
5
6
if ((a == null && b != null) ||
(a != null && b == null) ||
!a.equals(b)) {
}
// WAIT, if a == null and b == null ?
// SHOULD Objects.equals(a, b);

有內建方便使用的函數,別再這樣寫了。

Sync-Up

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Record {
boolean merge() {
return a.setParent(b.getParent().copy());
// refactor, a.setParent(b.getParent());
}
}
class Merger {
void process(Record r) {
try {
merge();
} catch (Exception e) {
// nothing here
}
}
}

為什麼偷偷搞了一個 Exception 又不給 log message,一不小心就 refactor 到奇怪的東西,曾經把 NullPointerException 視為一個正常邏輯,修掉來自另一個 call path 的 NPE,卻又踩了另一塊的雷。

Keyboard

1
2
3
4
SHIFT + INSERT = CTRL + V
CTRL + C
CTRL + Z + ENTER
HOME/END

大家耳熟能詳的 CTRL C+V,都不知道 CTRL+INSERT/SHIFT+INSERT,而在 X Window System 上,選取時會自動複製,不用額外按下複製操作。

至於問 INSERT/DELETE/HOME/END 是什麼的小夥伴,還是好好看清楚鍵盤吧。

Read More +

Company Ghost Story 公司鬼故事 13

Java

Default Constructor Trap

1
2
3
4
5
6
class Point {
Point() {
throw new UnsupportedOperationException(); // why?
}
Point(int x, int y) {...}
}

一開始不宣告就好了,當有自定義的建構子,預設建構子自然無法被使用,沒必要設一個陷阱讓別人踩。

Feature and Backward Compatibility

1
2
3
4
5
6
class Engine {
private String mFeature = ""; // Why not null ?
}
class DbObj {
private String mFeature = ""; // Why not null ?
}

空與空字串的意義不同,預設為空字串 ("") 的狀況並不多見,為空 (null) 在序列化和反序列化時的差異就很大了。

Fancy Check-and-Cast

1
2
3
Shape shp;
if (shp.getUserName().equals("circle")) // wait, where is your `instanceof`
return ((Circle) shp).getBound();

Call-By-Value

1
2
3
4
5
void readColRow(int col, int row) {
Pair p = readPair();
col = p.first;
row = p.second;
}

Java 沒有 reference type 的這操作。

Slow Feature

1
2
3
void exec() {
for (;;) {} // where is throw new UnsupportedOperationException();
}

我可不想跟客戶說「你再等等,這功能只是比較慢」的謊言。

Read More +

Company Ghost Story 公司鬼故事 12

Java

Fancy OOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MSTAlgorithmEngine {
JButton mApplyButton; // why, ???
JPanel mUI; // why, ???
}
class DiffRecord {
Action mMergeAction; // why, ???
Action mShowAction; // why, ???
}
class Rectangle {
void normalize() {
new NormalizeAction().actionPerformed(new ActionEvent(ActionEvent.PERFORMED));
} // I cannot deal with this shit anymore.
class NormalizeAction extends AbstractAction {
// real implementation for model class
}
}

資料歸資料,算法歸算法,別把 UI 牽扯進來。

Shit Cache

1
2
3
4
5
6
7
8
class Engine {
private static CacheStructure sCache;
public void exec() {
resetCache();
// fill cache;
} // but, when will you clear sCache, sCache = null?
}

快取的污染啊,那些殘存的資料依舊卡在記憶體內,是無法被 GC 回收的。反而,造成下一個運行的更容易出現記憶體不足。

Shit Cache in Class

1
2
3
4
5
6
7
8
9
10
11
class Dbo extends DbObject {
private CacheStructure mCache; // for internal recursion, why this thing is here?
public Object getSomething() {
// init cache
return internalGetSomething(parameters);
} // but, when will you clear cache, cache = null?
private Object internalGetSomething(parameters) {
// ...
}
}

別亂放你的資料結構,這關資料庫物件什麼事?

Read More +

Company Ghost Story 公司鬼故事 11

Java

Over Overload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OhMyGod {
public void run() {System.out.println("A");}
public void run(String t) {System.out.println("B");}
public <T> void run(T[] t) {System.out.println("C");}
public <T> void run(T t) {System.out.println("D");}
public static void run(String... t) {System.out.println("E");}
public static void main(String[] args) {
new OhMyGod().run();
new OhMyGod().run(new String[0]);
new OhMyGod().run("1");
new OhMyGod().run("1", "2");
}
} // AEBE, little strange

One-Format Multiple-Ways

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FileIn {
private boolean isCSV; // why not enum
private boolean isXML;
private boolean isJSON;
public void setIsCSV(boolean e) {isCSV = e;}
public void setIsXML(boolean e) {isXML = e;}
public void setIsJSON(boolean e) {isJSON = e;}
public void import(File file) {
// why not if-else
if (isCSV) CSVIn(file); //???
if (isXML) XMLIn(file); //???
if (isJSON) JSONIn(file); //???
}
}
Read More +

Company Ghost Story 公司鬼故事 10

Java

Compare Function

1
2
3
4
5
6
7
Collections.sort(arr, (x, y) -> (int) (x.cost - y.cost));
Collections.sort(arr, (x, y) -> Long.valueOf(x).compareTo(y));
Collections.sort(arr, (x, y) -> (int) Math.round(x.cost - y.cost));
// why not use official methods?
Collections.sort(arr, (x, y) -> Double.compare(x.cost, y.cost));
Collections.sort(arr, (x, y) -> Long.compare(x, y));

別鬧了,會 overflow/underflow,數量一大,每一個排序都在丟例外給我啊。

Partition Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Optimizer {
private int avgX(ArrayList<Point> pts) {
ArrayList<Long> x = new ArrayList<>(pts.size());
for (Point p : pts)
x.add(p.x);
return avg(x);
}
private int avg(ArrayList<Long> xs) {
long sum = 0;
for (Long x : xs)
sum += x;
return sum / xs.size();
}
}
// ... why not merge avg() into avgX()?

每次都要額外建一個陣列,而且這個函數只有被內部的單一函數呼叫,函數切這麼細,但是效能卻爛掉。

Try-Catch vs. If-Else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Parser {
void addChar(char c) {
try {
que[index] = c;
} catch (ArrayIndexOutOfBoundsException e) {
doubleArray();
que[index] = c;
}
index++;
}
}
// why not just write if-else statements, it's really hard to maintain your code.
class Parser {
void addChar(char c) {
if (index >= que.length)
doubleArray(index);
que[index] = c;
index++;
}
}

如果在修改這幾段代碼的時候,還得經常考慮例外處理是怎麼做的,這寫法真的太奇葩。要是開剖析器去檢查,會經常看到例外出現,那到底是好事還壞事?

Read More +

Company Ghost Story 公司鬼故事 9

Java

BufferWriter/StringBuilder

1
2
3
4
5
private void writeLong(long v) {
StringBuilder sb = new StringBuilder(8192); // ?
sb.append(Long.toString(v));
writer.write(sb.toString());
}

每一次轉換,別浪費額外的空間 8192 的 StringBuilder。如果真的需要這種 IO 的緩衝區,可以直接宣告在 BufferWriter 給定預設大小的緩衝區。

BufferWriter/StringBuilder

1
2
3
private String writeTwo(String token0, String token1) {
writer.write(token0 + token1); // why not write separately without intermediate result.
}

既然都知道了字串串接會有中間結果,我們可以直接分開寫 writer.write(token0);writer.write(token1); 來達到相同效果。

StringBuilder Helper

1
2
3
4
5
6
7
8
9
10
private static StringBuilder append(StringBuilder sb, String... tokens) {
for (String t : tokens)
sb.append(t);
return sb;
}
append(sb, "a", "b", "c").append("d", "e");
// if performance it critical, please write all statements without sugar syntax
append(ab, "a" + "b", "c");
// ... why?

這種可變參數的寫法會額外產生一個陣列,對於效能來說會很傷。

Math Getter/Setter

1
2
3
4
5
// convert x' = a x + b
private void convert(Point p) {
p.setX(p.getX() * a);
p.setX(p.getX() + b); // why not p.setX(p.getX() * a + b);
}

別中毒於 Java 或者是 OOP 的寫法。

Lookup by Key

1
2
3
4
5
6
7
8
9
10
public T getSelected() {
for (T c : candidates) {
if (Objects.equals(c.toKey(), mSelected))
return c;
}
return null;
}
public void setSelected(T s) {
mSelected = s.toKey(); // why not store `s` directly?
}

在大多數的狀況下都可以直接存原本的物件。除非是在一些特定的序列化,或者是橫跨兩個不同 domain 的交互資料,這時候我們必須採用字串作為中介對象。

Default Value by Wrapper Class

1
2
3
4
5
6
7
8
9
10
11
private Boolean mirror = false; // why use wrapper class here?
public void setMirror(boolean m) {
mirror = m;
}
private void process() {
if (mirror != null) { // why?
// must do here
}
}

多餘的寫法。如果 wrapper class 有預設值,而且還不能被設置 null。那必然有些地方出錯。

Constant Value by Wrapper Class

1
2
3
4
5
private static final Double SIZE = 50L; // ...
public void exec() {
long v = SIZE.longValue(); // ...
}

這傢伙到底在宣告什麼?想用什麼?

Read More +

Company Ghost Story 公司鬼故事 8

Java

Format String

1
2
String.format("ABC%s", "D"); // const string should be written directly
String.format("%s.%s", A.class.getName(), "C");

對於可以直接計算得到的,應該直接展開。

Math

1
2
3
4
5
6
7
8
9
10
11
12
13
14
double theta = dx != 0 ? Math.atan(dy/dx) : (dy < 0 ? ... : ...);
// or
if (dx < 0) {
if (dy < 0)
...
} else (dx == 0) {
if (dy < 0)
...
}
// why not be simple?
double theta = Math.atan2(dy, dx);

數學很重要,可以減少很多分支判斷。

Variable Name

1
2
List<T> set = new LinkedList<>(); // what does `set` means?
// perhaps, I need to ask what does `list` means for you.
1
2
TextField okBtn; // why name button for text field?
enableCancelButton(okBtn); // are you sure about it?

命名盡量貼切型態,不然有時候真的會有很奇怪。

Initialize Member

1
2
3
4
5
6
7
8
9
10
11
12
class MPanel {
private JButton mButton;
public MPanel() { // consturctor
...
mButton = new JButton("A"); // what?
panel.add(mButton);
mButton = new JButton("B"); // what?
panel.add(mButton);
// finally, which result you want to express mButton?
}
}

在建構子中,每一個成員變數盡量只存放一次,採用 final 對每個成員變數檢查只初始化一次。

Lambda with Final

1
2
3
4
5
6
7
void process(...) {
String s = "default";
if (isA())
s = "A";
final String _s = s; // shut up, don't explain
blackhole(() -> map(_s));
}

Lambda 函數抓取的時候都要抓取可推斷出 final 的變數。為了 lambda 而額外宣告一個變數作為 final 的話,應該要額外宣告一個 getter 函數去抓取,不然觀感上會覺得代碼有點多餘。

Method Name

1
2
3
4
void increment() { // why is noun?
i++;
i++; // why call it twice?
}

函數盡量使用動詞開頭,名詞有點奇怪。

Context

1
2
3
4
5
6
7
void enableA() {
a = true;
b = true; // why is b here?
}
void disableA() {
a = false; // wait ... where is B?
}

當名稱對應到成員變數時,就應該一件事情對應一個操作。

Read More +

Company Ghost Story 公司鬼故事 7

Java

try-catch-exception

1
2
3
4
5
6
7
8
9
10
11
Object getSomething() {
final Exception abort = new RuntimeException();
try {
// ...
} catch (Exception e) {
if (e == abort)
// fine
else
e.printStackTrace();
}
}

每次呼叫這個函數都要先建立一個例外,然後看裡面會不會丟出來,這個其實很浪費空間和時間建立。可以考慮建立一個特殊 AbortException 來取代預先建立的例外,或者建立一個全局的靜態例外物件來使用。

Double Brace Initialization

1
2
3
4
5
6
7
8
9
10
11
12
final Set<String> set = new HashSet<> {
{
add("A");
add("B");
add("C");
} // no more
};
// why not?
set.add("A");
set.add("B");
set.add("C");

這會額外建立匿名類別。如果宣告在一般的成員方法中,還會捕抓成員變數,造成內存洩漏而無法回收的狀況。

No More Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
class Point extends Pair<Integer, Integer> {
Point(int x, int y) {
super(x, y);
}
@Override
public boolean equals(Object o) {
if (o instanceof Point) {
Point p = (Point) o;
return first == p.first && second == p.second; // wrong
}
return false;
}
}

別忘了,Java 中的 wrapper class 可是一個物件,光靠 == 是不能比較相同與否。雖然對於數值小於某個定值的時候,他們會在常數內存池中取得,讓你覺得一切都好像沒事。

Read More +