Company Ghost Story 公司鬼故事 1

微信扫一扫,分享到朋友圈

Company Ghost Story 公司鬼故事 1

以下出現的問題,都是在公司遇到的事件,以類比的方式來描述遇到的狀況。

Java

Unique

class DbObj{
@Override
public boolean equals(Object obj){
return this.compareTo((DbObj) obj) == 0;
}
}

對於唯一存在的物件,別用 compareTo()
來實作 equals()
,它們可以直接用 ==
判定。而且大部分的 compareTo
是比較慢的線性實作。如下述的物件

class MyString{
@Override
public boolean equals(Object obj){...}; // possible O(n)
}

結果就是變得超慢。

toString
in Comparison

class CompositeClassimplements Comparable<CompositeClass>{
@Override
public int compareTo(CompositeClass other){
return toString().compareTo(other.toString()); // ???????
}
}

千萬別這麼幹,當我們對類別增加成員時,同時修改 toString()
後,運行結果也不同。這也會帶來嚴重的效能問題,試想著排序的時候,產生一大堆的字串用於比較,那麼垃圾回收就會佔有大部分的時間。

toString()
in Key

當需要對 enum
或者其他相關物件進行反序列時,避開 toString()
,因為這個函數很容易被其他人覆寫。

enum SomeType {
@Override
public String toString(){
return super.toString() + "...";
}
}
SomeType.valueOf(type.toString()); // ?????

有人會說這個函數不該被覆寫,但能操作就可能出事。

It is not in C++

boolean sameLocation(Point a, Point b){
return a == b; // ?????, Objects.equals(a, b)
}

抱歉,您使用的是 Java,並沒有 operator==
的語法,所有的 ==
都是比較相同物件,並不會實作對應的 equals()

Numeric Comparison

static final Comparator<Point> compareX =
(a, b) -> a.getX() - b.getX(); // X, Long.compare(a.getX(), b.getX());

這種容易發生 overflow/underflow 的寫法,並不建議模仿 C/C++ 的習慣,請多用內建的函數比較。

this
in Constructor

public Point {
Point(long x, long y) {
mX = x;
mY = y;
}
Point() {
mX = 0; // X, this(0, 0);
mY = 0;
}
}

請盡量使用建構子,各自獨立容易漏掉一些共同的檢查。

Initialization

Pointcopy(){
Point pt = new Point();
pt.setX(getX());
pt.setY(getY());
return pt; // new Point(getX(), getY());
}

能使用建構子完成的事情就盡量使用,分次使用可能會造成過多額外的檢查或調整,效能就會往下掉。

Find the Minimum/Maximum Element

PointgetMax(List<Point> testList){
Collections.sort(testList);
return testList.get(testList.size() - 1); // Collections.max(testList);
}
PointgetMin(List<Point> testList){
return testList.stream().sorted().getFirst().orElse(null);
// testList.stream().min().orElse(null);
}

排序很簡單,但也不能亂寫。

排序複雜度大部分為 $O(n \log n)$,事實上找最小值只需要 $O(n)$。當 $n = 10^6$
時,效率就可能差到 20 倍。

Computed Getter

static final Comparator<CPoint> compateLocation =
(CPoint ca, CPoint cb) -> {
long p0x = ca.getCPt().getX();
long p1x = cb.getCPt().getX();
long p0y = ca.getCPt().getY();
long p1y = cb.getCPt().getY();
... // X, return ca.getCPt().compareTo(cb.getCPt());
}

請不要這麼寫,排版好看行數很多並不是好藉口,這呼叫了好幾次 getCPt()
,如果牽涉到好幾步複雜運算,整個效率就慢上了好幾倍。

Computed If-Else

if (getA() != null &&
getA().getB() != null &&
getA().getB().getC() != null) {
...
}

寫在同一個 if-statement 很方便,卻造成效能嚴重退化。不如蠢一點的寫法,或者透過 Optional<>
來描述回傳型態。

A a = getA();
if (a != null) {
B b = a.getB();
...
}
getA().ifPresent(a -> {
a.getB().ifPresent(...)
});

List Getter

LinkedList<Long> X;
void doSomething(List<Long> X){
for (int i = 0; i < X.size(); i++) {
long x = X.get(i);
...
}
}

別鬧了,這會出大事。效能殺手就是你。 LinkedList
get(index)
可是 $O(n)$ 的。

Immutable Methods

void parse(String elem){
elem.trim(); // ??????, elem = elem.trim();
...
}

記得把回傳值接起來,請不要陡增效能問題。

Method Reference

Map<Long, List<Point>> map;
void add(Point a){
List<Point> list =
map.computeIfAbsent(a.mX, key -> new LinkedList<>()); // X, Point::prepare
}
static List<Point> prepare(Long key){
return new LinkedList<>();
}

盡量使用 method reference,防止 memory leak,也降低 lambda metafactory desugar 的花費。

Boxing

DoublegetRotate(){ // X, double
double r = ...;
return r;
}
boolean getMirror(){...};
Boolean isMirror = getMirror(); // X, boolean

既然有預設值也確保不會發生 null
,請不要過度包裝。拆拆裝裝的狀況會造成垃圾過多。

Exception is NOT a Return Value

public String getFileExt(File f){
try {
String token = parseExt(f);
return "." + token;
} catch (NullPointerException e) {
return null;
}
}

請別接收這麼奇怪的 runtime exception。在 Java 中,exception 很昂貴的,造價就是整個 call stack。

也別這樣追蹤物件的建立,大量物件會造成內存不足。

class Point{
private Throwable trace = new Throwable();
}

Lightweight Exit

void process(Point p){
List<Long> array = new ArrayList<>(10000); // ?????
if (p == null)
return;
}

別急著建立一堆用不著垃圾,有可能在後續判斷中直接返回。盡量縮小變數的生命週期,快用到的時候再準備計算資源。

If-Elif-Else

if (token.equals("a"))
{
}
else if (token.equals("b"))
{
}
if (token.equals("c"))
{
}

別說排版不重要,那一定是沒看過有人不小心漏了什麼。在大部分情況不太會出事,只是多判斷了幾次造成效能退化。

Dead instanceof

public abstract class Shape;
public class Polyextends Shape;
public class Rectextends Poly;
void write(Shape shape){
if (shape instanceof Poly) {
} else if (shape instanceof Rect) {
// ?????, unsearchable
}
}

凡事有先後,請注意繼承關係。

Observer Pattern

private List<ObsListener> list = new LinkedList<>();
private Set<ObsListener> list = new HashSet<>();

當訂閱者數量不少,且容易進進出出,請不要用線性的 LinkedList
。當你需要嚴格地再現 BUG,就不要使用不穩定順序的 HashSet
,請使用 LinkedHashSet
是一種保守的選擇。

Remove Observer

class CreateUIextends Dialog{
public CreateUI(){
db.addListener(mChangeListener);
}
// where is your db.removeListener
}

別忘記移除掛載到資料庫的觀察者,這樣往往覆覆操作,越來越慢真的不能怪人。

Opposite Behavior

assert pathA.endsWith(pathB) ==
pathB.isEndOf(pathA); // fail ??????

英文函數命名上,操作對稱就該對稱。英文不好要先說,邏輯不好也先說一聲,大家可以幫忙的。

Global Garbage

static List<DbObj> tmp;
List<DbObj>getXXX(){
tmp = new LinkedList<>();
parallel...
return tmp;
}

別擠壓到下一個人的生存空間,要是沒有使用 getXXX
,會看到一堆資料庫物件被卡在全區變數裡頭。

Useless Argument

class Path{
boolean startsWith(Path p);
boolean startsWith(Path p, Object a); // new one
}

別因為不想改動原本的,建立一個一模一樣名字的函數,而且第二個參數並沒有使用到,overloading 不是這樣子設計的。

Count

int getPointCount(){
return mPoints.parallelStream().count();
// return mPoints.size();
}

可以覺得慢,但不要總是開平行解決事情。這種計數問題,通常都有相關的方法可以呼叫。如果沒有,請聯絡相關人士。

Tooltip

String tooltip = ""; // should StringBuilder
for (DbObj t : db.getObjects(XXX.class))
tooltip = tooltip + t.toString();

沒人說這樣不好,除了串接消耗 $O(n^2)$,一般也不會把所有東西拉出來。

Convert Set to Array

List<Point>toArray(Set<Point> set){
List<Point> arr = new ArrayList<>();
for (Point e : set)
arr.add(e);
return arr; // return new ArrayList<>(set);
}

建構子更方便,別這麼辛苦,效能至少慢了兩倍。

演算法還是有它的極限在的,常數的確不是很重要,但是數量一大的時候,均攤造成額外操作造成的垃圾,在 Java 中會變得相當明顯。

Error Message

IntegerparseInteger(String x){
String errMsg = x + "is an invalid integer format...";
errMsg = attachCurrentLineMessage(errMsg);
try {
Integer n = Integer.parseInt(x);
return n;
} catch (Exception e) {
System.err.println(errMsg);
return null;
}
}

人家還沒出錯,先別急著準備錯誤訊息。提前準備的字串複雜度比處理的複雜度還高,這絕不允許。

New Option

static void import(boolean a);
static void import(boolean a, boolean b);
static void import(boolean a, boolean b, int c);
static void import(boolean a, boolean b, int c, String d);
...

別再為了新參數往上疊,請用更容易看出參數意義的 builder 的寫法。

越來越多的參數,造成容易寫錯傳參的順序的悲劇,這時候 BUG

微信扫一扫,分享到朋友圈

Company Ghost Story 公司鬼故事 1

MapReduce和YARN基础面试题总结

上一篇

Node.js VS 浏览器以及事件循环机制

下一篇

你也可能喜欢

Company Ghost Story 公司鬼故事 1

长按储存图像,分享给朋友