2016年5月29日 星期日

從定義看重構

2016/5/29 14:06~14:45


圖片取自 <泰迪軟體-軟體重構入門實作班>投影片

重構就是『在不改變程式外在行為的前提之下改變城市內部結構以提升設計品質』

Teddy從定義切入引伸出了六個議題:

1. 如何確認程式外在行為沒有被改變?


在正常的軟體開發過程中,通常會先規劃好設計規格(或類別),才開始動手寫程式,然後經過手動或自動化測試驗證功能是否符合規格。
程式會依時間或需求而變得越來越大坨,測試項目也會跟著越來越多,假設目前的程式已開發了100個功能,當你重構一小塊功能,通常要重新測試這100個功能,以確保自己修改的程式沒有問題。這時候如果沒有自動化測試的話通常開發人員也會開始不敢改程式,更別說整理程式碼了。

2. 如何定義哪些是外在行為?


一個使用者使用計算機類別的類別的public "add" 方法,對使用者來說,add的行為就是外在行為,計算機使用者來說是一個黑箱,我不知道它怎麼做,但我知道它會說。

3. 為什麼要以不改變程式外在行為當作前提?


這個問題很有趣,為什麼不能再新增功能的同時順便重構?
不知道你有沒有這樣的體驗,在新增功能的過程中,發現某一塊的程式碼寫了100行達成一個功能,但你發現其實寫10行就可以達到一樣的功能,所以你修改這一塊程式碼,改完之後才繼續做原本要新增的功能,好不容易把功能新增完,很興奮地按下"執行",再來你的情緒就跟著程式一起崩潰了,是剛剛重構錯誤,還是新增的功能錯誤?

4. 怎麼改變程式內部結構以提升品質?


藉由重構裡面所定義的壞味道與技巧,加上自己的反覆練行。想知道怎麼改可以讀Martin Flower的重構或者上泰迪軟體-軟體重構入門實作班。

5. 那些內部結構可以改?


以Java來說,程式碼的結構從小到依序為
變數(Variable) → 條件式(Statement) → 方法(Method) → 類別(Class) → 包裹 (Package)
重構裡面所談的重構技巧也是有結構上的差別,例如調整類別的繼承關係、改變方法的介面、將1個100行的方法切成5個20行的方法等等。

6. 品質提升的目的為何?


讓軟體變軟


Teddy總是能把死的定義講得很活真的有很猛

2015年11月5日 星期四

Android StackTrace

2015/11/05 15:10~15:31

在C語言中,Log時可以使用 __func__ 及 __LINE__ 取得目前程式所執行的函式和行數,在Android中的StackTraceElement物件也提供了類似的功能,它記錄了Call stack的許多資訊,包括執行檔案名稱、程式碼行號、方法(或函數)名稱、類別名稱。

程式範例:

package com.example.getstacktrace;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         trace("test");
    }

    private void trace(String msg) {
         StackTraceElement[] stack = Thread.currentThread().getStackTrace();        
         for(int i = 0; i < stack.length; i++) {
             StackTraceElement s = stack[i];
             Log.d("Brian", "i=" + i);
             Log.d("Brian", "ClassName="+s.getClassName());
             Log.d("Brian", "FileName="+s.getFileName());
             Log.d("Brian", "MethodName="+s.getMethodName());
             Log.d("Brian", "LineName="+s.getLineNumber());      
             i ++;
         }
         Log.d("Brian ", msg);
    }

}

輸出結果:
11-05 15:25:49.748: D/Brian(6662): i=0
11-05 15:25:49.748: D/Brian(6662): ClassName=dalvik.system.VMStack
11-05 15:25:49.748: D/Brian(6662): FileName=VMStack.java
11-05 15:25:49.748: D/Brian(6662): MethodName=getThreadStackTrace
11-05 15:25:49.748: D/Brian(6662): LineNumber=-2
11-05 15:25:49.748: D/Brian(6662): i=1
11-05 15:25:49.748: D/Brian(6662): ClassName=java.lang.Thread
11-05 15:25:49.748: D/Brian(6662): FileName=Thread.java
11-05 15:25:49.748: D/Brian(6662): MethodName=getStackTrace
11-05 15:25:49.748: D/Brian(6662): LineNumber=599
11-05 15:25:49.748: D/Brian(6662): i=2
11-05 15:25:49.748: D/Brian(6662): ClassName=com.example.getstacktrace.MainActivity
11-05 15:25:49.748: D/Brian(6662): FileName=MainActivity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=trace
11-05 15:25:49.748: D/Brian(6662): LineNumber=16
11-05 15:25:49.748: D/Brian(6662): i=3
11-05 15:25:49.748: D/Brian(6662): ClassName=com.example.getstacktrace.MainActivity
11-05 15:25:49.748: D/Brian(6662): FileName=MainActivity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=onCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=12
11-05 15:25:49.748: D/Brian(6662): i=4
11-05 15:25:49.748: D/Brian(6662): ClassName=android.app.Activity
11-05 15:25:49.748: D/Brian(6662): FileName=Activity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=performCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=5165
11-05 15:25:49.748: D/Brian(6662): i=5
11-05 15:25:49.748: D/Brian(6662): ClassName=android.app.Instrumentation
11-05 15:25:49.748: D/Brian(6662): FileName=Instrumentation.java
11-05 15:25:49.748: D/Brian(6662): MethodName=callActivityOnCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=1103
11-05 15:25:49.758: D/Brian(6662): i=6
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=performLaunchActivity
11-05 15:25:49.758: D/Brian(6662): LineNumber=2416
11-05 15:25:49.758: D/Brian(6662): i=7
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=handleLaunchActivity
11-05 15:25:49.758: D/Brian(6662): LineNumber=2521
11-05 15:25:49.758: D/Brian(6662): i=8
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=access$600
11-05 15:25:49.758: D/Brian(6662): LineNumber=162
11-05 15:25:49.758: D/Brian(6662): i=9
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread$H
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=handleMessage
11-05 15:25:49.758: D/Brian(6662): LineNumber=1370
11-05 15:25:49.758: D/Brian(6662): i=10
11-05 15:25:49.758: D/Brian(6662): ClassName=android.os.Handler
11-05 15:25:49.758: D/Brian(6662): FileName=Handler.java
11-05 15:25:49.758: D/Brian(6662): MethodName=dispatchMessage
11-05 15:25:49.758: D/Brian(6662): LineNumber=99
11-05 15:25:49.758: D/Brian(6662): i=11
11-05 15:25:49.758: D/Brian(6662): ClassName=android.os.Looper
11-05 15:25:49.758: D/Brian(6662): FileName=Looper.java
11-05 15:25:49.758: D/Brian(6662): MethodName=loop
11-05 15:25:49.758: D/Brian(6662): LineNumber=158
11-05 15:25:49.758: D/Brian(6662): i=12
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=5777
11-05 15:25:49.758: D/Brian(6662): i=13
11-05 15:25:49.758: D/Brian(6662): ClassName=java.lang.reflect.Method
11-05 15:25:49.758: D/Brian(6662): FileName=Method.java
11-05 15:25:49.758: D/Brian(6662): MethodName=invokeNative
11-05 15:25:49.758: D/Brian(6662): LineNumber=-2
11-05 15:25:49.758: D/Brian(6662): i=14
11-05 15:25:49.758: D/Brian(6662): ClassName=java.lang.reflect.Method
11-05 15:25:49.758: D/Brian(6662): FileName=Method.java
11-05 15:25:49.758: D/Brian(6662): MethodName=invoke
11-05 15:25:49.758: D/Brian(6662): LineNumber=511
11-05 15:25:49.758: D/Brian(6662): i=15
11-05 15:25:49.758: D/Brian(6662): ClassName=com.android.internal.os.ZygoteInit$MethodAndArgsCaller
11-05 15:25:49.758: D/Brian(6662): FileName=ZygoteInit.java
11-05 15:25:49.758: D/Brian(6662): MethodName=run
11-05 15:25:49.758: D/Brian(6662): LineNumber=1083
11-05 15:25:49.758: D/Brian(6662): i=16
11-05 15:25:49.758: D/Brian(6662): ClassName=com.android.internal.os.ZygoteInit
11-05 15:25:49.758: D/Brian(6662): FileName=ZygoteInit.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=850
11-05 15:25:49.758: D/Brian(6662): i=17
11-05 15:25:49.758: D/Brian(6662): ClassName=dalvik.system.NativeStart
11-05 15:25:49.758: D/Brian(6662): FileName=NativeStart.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=-2
11-05 15:25:49.758: D/Brian(6662): test



挑自己需要的StackTraceElement包成Method就很好Debug了!

2015年10月26日 星期一

印出環境變數

2015/10/26 11:38~11:54


在Windows中,很多時候需要用命令提示字元觀看環境變數,在Powershell中,這時可以使用下面的命令

$env:Path

輸出結果:

C:\Windows\Microsoft.NET\Framework\v4.0.30319;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;C:\ProgramFiles\TortoiseSVN\bin;C:\HashiCorp\Vagrant\bin;C:\Program Files\SourceGear\Common\DiffMerge\;C:\Program Files (x86)\Skype\Phone\


在Windows的環境變數中,每一個路徑使用分號隔開,直接印出來很難閱讀有哪先路徑。


這時可以把分隔符號分號取代為跳行,結果如下:

($env:Path).Replace(';', "`n")

輸出結果:

C:\Windows\Microsoft.NET\Framework\v4.0.30319
C:\ProgramData\Oracle\Java\javapath
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Program Files\Microsoft SQL Server\110\Tools\Binn\
C:\ProgramFiles\TortoiseSVN\binC:\HashiCorp\Vagrant\bin
C:\Program Files\SourceGear\Common\DiffMerge\
C:\Program Files (x86)\Skype\Phone\

Powershell用起來比命令提示字元順好多!

2015年7月29日 星期三

持續改善實戰演練

2015/7/28 12:25-13:27,
2015/7/29 00:31-01:14


今天打的這篇將會出現一位未出現過的人物,名為Joanne,是一位嚴重受科技控制的女孩,一起出去時不時就會拿起手機喵一下是否有新訊息,吃飯吃到一半也不忘要拿起我的Lumia 925,技術純熟地開啟我的FB塗鴉牆或者instagram,看看我有沒有偷偷跟哪個小妹妹聯絡感情。看到這邊應該可以猜出她和我的關係了吧?沒錯!她就是我家的那隻啦XD

---

事情發生在7/25 (六)

這天很巧,和可愛的Joanne大人去吃一家員林赫赫有名的希拉餐廳,燈光美氣氛佳,可惜我那不給力的FB塗鴉牆出現多年不見的要好女性友人PO了疑似看見我的文章。

離開希拉之後,路過一間名字聽起來不錯的迥畫廊咖啡,點了幾杯飲料開始接受Joanne的質問。

---

Joanne:「她PO這個是因為看到我們嗎?」

我:「不會吧!我已經仔細環顧四周了,沒有看到我認識的人啊!」

Joanne:「她幹嘛PO的好像在躲我們一樣,這樣真的很不夠意思!」

我:「她的動態對我們來說也沒有很重要啊,這麼在乎幹嘛。」

---

講完之後我自己我有點生氣,為什麼要在意一個不是很重要的人這麼多!於是我專心看著咖啡店裡的書,Joanne也很有默契地滑著手機,我倆不發一語,一直到我看到第26頁的時候,終於突破僵局手拉著手離開咖啡廳。然後從此過著幸福快樂的一天~~~了嗎?

當然沒有,這只是個小小的火種,悄悄的埋在Joanne的心中。

回到家約晚上七點多,老爸一如往常地看到我們見念聊個兩句

老爸:「Joanne啊,你很不夠意思欸!我以為你今天回來會煮飯給我吃,害我等你們等這麼久」(老爸是講中文,以上對話純屬修改)

我聞到Joanne生氣的壞味道,趕緊出面緩和一下場面

我:「老爸,Joanne最近在台北工作很累,她是回來渡假休息的啦!不要為難一個正在渡假的人啊!」

老爸:「那我去煮個拿手的泡麵來孝敬你們兩個正在渡假的人要不要?」

講到這邊Joanne就委屈的跑去廁所。

老爸又接著說:「Joanne怎麼時常跑廁所,你要關心一下她。」

說完老爸也出去溜搭了,然後我開始看著週六晚檔,連播四集,共兩小時的動畫。

身為一個電視迷,說什麼也要排除順利觀賞節目的阻礙,我專心地看著電視裡生動的動畫,Joanne從廁所出來後,就零音量地坐在一旁玩筆電、手機。就連在動畫廣告時,我故意向空氣發出的問句,她也不為所動。但為了順利觀賞動畫演出。顧不得Joanne的任性,只好努力地催眠自己沒有任何事情發生。

就在動畫演完的那一刻,我的感官神經從電視飛回到我的身體,驚覺到身旁有股能量準備爆發,雖想當作沒事地把時間拖過去。但想到如果不讓Joanne爆發,不就枉費了從Teddy學來的人生道理軟體工程嗎?

敏捷開發的最大優點就是能透過一連串的活動讓問題浮現出來,發現問題之後,才有機會對症下藥。於是心裡開始有個聲音默默出現:「如果連一個人都沒辦法搞定,那怎麼可能有辦法搞定整個團隊?」

我跨出了很勇敢的第一步:「你生氣是不是因為爸爸說你怎麼沒有煮菜給他吃?」

此話一出,馬上引來Joanne的情緒爆炸,配著滔滔不絕的眼淚和哽咽又有磁性地聲音:「我今天跟你說很多事情,你都在第一時間否定我,都說這是小事不用在意,向你表達我的感覺,卻沒有得到情緒上的安慰,在你們家我是一個外人,如連你都不站我的立場替想,那我不是很孤單委屈嗎?你爸爸這樣說,我怎麼知道是開玩笑,我們的家庭教育就是不一樣啊!#$%@$#%^%$#^%#$%^」 講的太長我也忘記到底在講什麼XD

不過算是發現了Joanne的情緒崩潰點算是有點進展,吵了將近三十分鐘後,套入Retrospective,將今天所有事件的處理方式列出優點和缺點。

Could be better:

1. 我們兩個的個性一個大而化之;一個心思縝密,要站在Joanne立場多位她說話。
2. 你(Brian)遇到問題每次都沉默
3. 你(Brian)應該是最了解我(Joanne)的人,會知道我(Joanne)的心情

Good:

1. 查覺到異狀有即時出手幫Joanne說話,雖然說得很爛(渡假是三小?家長聽了會怎麼想?)
2. 舉行Retrospective讓雙方有理性溝通的機會


講完之後就和好了,再努力再加油,朝著更幸福快樂的日子努力!

以上內容由事實改編。這次網誌Delay了三天。

2015年7月18日 星期六

為自己寫一個Pattern

2015/7/18 14:16-15:01

固定每個星期六來想一篇網誌的今天,剛剛好是自己的生日,在生日的這天,來反省一下自己。

目前覺得自己對一件事情的看法太少,常常讓我想起<少年Pi>裡面,Pi的父親和Pi說過的一句台詞:「如果你什麼都信,就代表著你什麼都不信。」

對於生活上瑣碎的事情,每當和死黨聊天的時候,多多少少會聊到一些對於社會議題的看法,我發現自己通常對於發生的事情都抱持著淡定的態度,其實想發表一些什麼,但卻發現自己講不出個什麼。呈現一個「腦死」狀態。

來練習一下把自己的狀況寫成Pattern。


Context:
€  一個平常對於任何事情或突發狀況都很淡定的人

Problem:
€  如何培養對於週邊事物發表個人看法的能力?

Force:
€  平常沒有動腦的習慣
€  發表自己的看法後怕別人見笑
€  身旁的人難以溝通,自己懶得費工
€  腦袋空空好像怎麼講都不對

Solution:
€  揪一個朋友看每天花同樣的時間看一樣的書,每天看完的當下就討論所看到的內容


前進速度緩慢

2015年7月12日 星期日

Martin Fowler整理的重構名錄

2015/7/2 20:34-21:20

前幾天終於把<重構>這本書讀完一次了,這本書有一半算是工具書,介紹非常多的重構手法,每個重構手法都以一個例子介紹,每個重構手法通常都可以找到它的情侶XD,原本正方形的東西不好看,所以凹成的長方形,凹長方形之後看起來有覺得怪怪的,於是又凹回了正方形。

在寫程式的時候,重構通常是為了讓程式碼更乾淨,但有時候還是會被"設計"綁住,對於還沒碰到的問題,有了過多的設計,也許在這個當下,你認為將來系統會遇到某些的擴充,所以你為了預防這個很久很久才會到來新需求或是非常偶爾才會出現的Special Case,開始重構程式,把原本功能簡單的程式碼切成很多Class去實作,以便以後可以應付這種Special Case。過了一段期間,才發現之前太杞人憂天了,很多預想的狀況根本都沒有發生,於是開始反重構,把很多冗員類別刪除。

這本書厲害的地方就在於每一個重構手法都以很小的步驟慢慢前進,在實際的工作中,可以先培養聞壞味道的能力,然後遇到某種壞味道的時候,在把這本書拿出來翻到最後一頁,參考下面的兩張表格,找出能解決這個壞味道的重構手法。


擷自<重構>最後一頁


擷自<重構>最後一頁

---



最近感冒,一直覺得頭好暈阿,差點兒沒發網誌。

讀完中文版的第一次,再去借了一本英文版,即使看過一次中文版的,英文版的讀起來還是頗硬阿!

先把一本原文書重頭到尾讀三次→Teddy Chen教我的那些事

2015年7月4日 星期六

程式碼的壞味道(3)

2015/7/3 21:58-22:44


上兩篇節錄了16個在重構中所提到的壞味道,
<程式碼的壞味道(1)>
<程式碼的壞味道(2)>

剩下6個壞味道,今天終於要把它們全部抄完節錄了


---

17. Inappropriate Intimacy (狎暱關係)

有時你會看到兩個classes過於親密,花費太多時間去探究彼此的private成分。如果這發生在兩個「人」之間,我們不必作衛道之士;但對於classes,我們希望他們嚴守清規。


18. Alternative Classes with Different Interfaces (異曲同工的類別)

兩個函式做同一件事,卻有著不同的署名式(signature)。


19.Incomplete Library Class (不完美的程式庫類別)

復用(reuse)常被視為物件的終極目的。我們認為這實在是過度估計了(我們只是使用而已)。但無可否認,許多編程技術都建立在library classes的基礎之上,沒人敢說是不是我們都把排序演算法忘得一乾二淨了。Library classes構築者沒有未卜先知的能力,我們不能因此責怪他們。畢竟我們自己也幾乎總是在系統快要構築完成的時候才能弄清楚它的設計所以library構築者的任務真的很艱鉅。麻煩的是library的形式(form)往往不夠好,往往不可能讓我們修改其中的classes使它完成我們希望完成的工作。


20. Data Class (純稚的資料類別)

所謂Data Class是指:它們擁有一些欄位(fields),以及用於存取(讀寫)這些欄位的函式,除此之外一無長處。這樣的classes只是一種「不會說話的資料容器」,它們幾乎一定被其他classes過分細瑣地操控著。


21.Refused Bequest (被拒絕的遺贈)

subclasses應該繼承superclass的函式和資料。但如果它們不想或不需要繼承,又該怎麼辦呢?它們得到所有的禮物,卻只從中挑選幾樣來玩!


22.Comments (過多的註釋)

從嗅覺上來說,Comments不是一種壞味道:事實上它們還是一種香味呢。我們之所以要在這裡提到Comments,因為人們常把它們當作除臭劑來使用。常常會有這樣的情況:你看到一段程式碼有著常常的註釋,然後發現,這些註釋之所以存在是因為程式碼很糟糕。這種情況的發生次數之多,實在令人吃驚。
Comments可以帶我們找到本章先前提到的各種壞味道。找到壞味道之後,我們首先應該以各種重構手法把壞味道去除。完成之後我們常常會發現:註釋已經變得多餘了,因為程式碼已經清楚說明了一切。

『當你感覺需要撰寫註釋,請先嘗試重構,試著讓所有註釋都變得多餘。』


如果你不知道該做什麼,這才是註釋的良好運用時機。除了用來記述將來的打算之外,註釋還可以用來你並無十足把握的區域。你可以在註釋裡寫下自己「為什麼做某某事」。這類資訊可以幫助將來的修改者,尤其是那些健忘的傢伙。


---

書中的這個章節,我覺得是全書最精華的地方,之前翻了一點<深入淺出程式設計>,雖然有認識到一些Pattern,但是一直無法找到應用某個Pattern的時機。

就在今天利用Extract Super Class重構的時候,突然發現重構完的地方可以套用Command Pattern,突然有一種串起來的感覺,頗有成就感XD。



每個軟體工程師都應該來讀一下重構這本書。