My friends, my life, my style - James S.F. Hsieh

2/28/2009

典型錯誤 Main

以下打勾是我遇過(V) 或見過(O) 的"典型錯誤", 所有項目摘錄於 Chapter 3 of RAPID Development - Steve McConnell, 找幾天來寫一寫每個遇見項目的故事與心得 人員
  • 錯誤的激勵方式
  • 素質低落 (V)
  • 對有問題的員工失控
  • 英雄主義造成忽略或不承認問題的真相
  • 專案後期忽然投入大量的人力(The Mythical Man-Month) (V)
  • 環境吵雜 (V)
  • 開發人員與客戶之間發生摩擦
  • 不切實際的預期 (V)
  • 缺乏有效的高層支持 (V)
  • 缺乏各種角色的齊心協力 (V)
  • 缺乏用戶的投入 (V)
  • 政治凌駕於專案本質 (V)
  • 充滿想像,沒有確切的計畫與證據支持想法 (V)
流程
  • 過於樂觀的進度計畫 (O)
  • 缺乏足夠的風險管理 (V)
  • 委外承包商導致的失敗
  • 缺乏計畫 (V)
  • 在壓力下放棄計畫
  • 在模糊未明的專案前期浪費時間 (O)
  • 前期活動時間不足 (V)
  • 缺乏足夠的設計 (V)
  • 缺少品質保證 SQA/QA 措施 (V)
  • 缺乏管理控制
  • 太早或過於頻繁的整合 (V)
  • 專案估算時遺漏必要的任務 (V)
  • 追趕計畫時程,反而造成品質的低落 (V)
  • 魯莽程式設計(code-like-hell) (V)
技術
  • 銀彈綜合症
  • 高估了新工具技術或方法帶來的節省量
  • 專案進行中更換工具
  • 缺乏自動化的程式碼版本控制 (V)
產品
  • 需求的無限上綱 (O)
  • 功能蔓延
  • 開發人員的迷失目標 (O)
  • 又推又拉的協商 (V)
  • 研究導向的開發

2/27/2009

x64 Part 3: 容易造成 x64 Migration 失敗的程式陷阱 1-2

  1. 指標存取與型態轉換 指標的 size 在 x86 跟 x64 下有了改變, 以下的程式碼為無法正常運作, 主要是因為指標的值轉換成 DWORD 時已經被截短了, 但是由於濫用強制轉型, 編譯器不會產生任何的警告或是錯誤
    // size of DWORD is 32 bits, size of pointer is 64 bits DWORD dwValueA = 0, dwPointer = (DWORD*)&dwValueA; DWORD dwValueB = *(DWORD*)dwPointer;
    假設指標的大小是固定的, 所以該程式碼是不具有移植性的. 問題的解法是不應該將數值型態與指標型態的資料相互轉換與使用, 避免使用強制轉型或是 static_cast 這樣的手法. 許多問題發生在人們想建立一個萬用的函數界面, 這個介面將來可以傳遞任何的引數, 以下就是個例子, 我們可能用 dwSetting 來傳遞一個指標, 這時候上述的問題就會發生
    STDMETHODIMP CMyCOM::PutSetting(DWORD dwSettingID, DWORD dwSetting) {...} SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyWndProc); //WIN32 API
  2. 大小相依的運算 由於 VC++ 使用 LLP64 的 data model, 所以只有指標的 size 有所改變, 像是 short, int, long, long long 的大小都跟原本 x86 的環境一致, 但是其他的編譯器與作業系統就不一定會是如此, 例如 Linux, Mac OS X, FreeBSD 就是使用 LP64 的 data model 以下的程式碼結果就會跟預期不同
    unsigned long valueA = 0x12345678, valueB = valueA << 16; // x86: valueB == 0x56780000 // x64 LP64: valueB == 0x0000123456780000
    預期結果有所改變是因為 unsigned long 的大小已經改變了, 而 shift operation 是跟 type 大小有著相依的關係, 使用大小可移植性的 type 可以解決這個問題, 舉個例子, 在 Windows 平台下 DWORD 恆常都是 32 位元無號數, 不管在 MIPS, x86, X64 或是 IA64 都是如此. 其實我們可以用一些手法來產生這樣的型態定義, 並且讓他可以跨機器跨平台. 我們可以使用 ANSI C 定義的 limits.h 來得知 native type 的 size.

#include <limits.h>

// 32 Bits 單位型別 #if (UCHAR_MAX == 0xffffffff) typedef unsigned char DUNIT32; typedef char DUNIT32S; #elif (USHRT_MAX == 0xffffffff) typedef unsigned short DUNIT32; typedef short DUNIT32S; #elif (UINT_MAX == 0xffffffff) typedef unsigned int DUNIT32; typedef int DUNIT32S; #elif (ULONG_MAX == 0xffffffffUL) typedef unsigned long DUNIT32; typedef long DUNIT32S; #else #error 不支援 32 位元寬的型別 #endif

2/22/2009

2009 02 22 菜公坑 燒炭古道

菜公坑的燒炭古道位於巴拉卡公路東側的山谷, 西側緊鄰巴拉卡山與烘爐山, 東側遠方則是嵩山, 全境屬於陽明山國家公園的屬地, 之所以稱之為燒炭古道, 主要是因為此處有多座燒炭窯的遺跡, 十六世紀時, 三芝有著許多凱達格蘭平埔族的零星聚落, 而木炭是當時主要的生活必需品,  到了清朝光緒年間, 三芝主要的特產是當地的茶葉, 烘培茶葉所需要的木炭更是不可或缺, 於是, 當時的人們開始在菜公坑旁邊的烘爐山種植相思樹和杉木, 以做為木炭的原料來源, 直到光復後燒炭的行業才逐漸沒落.
  • 08:30 泰北高中集合 
  • 09:30 二子坪 停車
  • 09:40 菜公坑登山口下拓 兩百公尺 
  • 11:14 羅厝坑溪   吃東西 拍照
  • 12:07 石厝遺址
  • 12:40 白色水泥儲水槽
  • 12:52 遇到好心的釣魚大哥開車將我們載到二子坪
  • 13:10 分道揚鑣
  • 13:30 北新庄
  • 13:45 大屯山溪場勘
  • 14:52 淡水 
  • 15:15 淡水黑店 嗑排骨麵
  • 15:45 沙崙拍照
  • 16:10 回程
檢視較大的地圖

2/17/2009

Coupling and Decoupling

軟體工程的耦合與相依有許多種形式, 而這些相依是不可能完全消除的, 因為唯有相依才能合作並達成目的, 以下這些相依是幾個常見相依關係

  • 編譯相依: 編譯時, 需要參考特定型別的定義或函數才能正常編譯
  • 行為相依: 執行時, 需要仰賴特定的流程, 協定, 或前置狀態才能正常運作
  • 模組相依: 執行時, 需要仰賴特定模組的服務才能正常運作
  • 潛在相依: 在軟體未來的發展中, 因為功能擴充或是程式誤用而產生的相依

以軟體工程來說, 單向相依比雙向來的好, 沒有相依比有相依來的好, 如果非得要有相依性, 那就將它收斂於特定少數的點, 這點其實跟 Dependency Inversion Principle 有著相同的的精神, 以下是 DIP 的想法

High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

DIP 耐人尋味的地方在於, 在過去軟體設計經驗中, 高階的模組相依低階的模組是天經地義的, 相反的, 不希望低階模組相依高階模組則是個較為容易達成的理想 (雖然台灣多數的軟體公司完全不考慮這些問題), 而 DIP 則認為上述的相依性仍然可以剔除, 其實, 關鍵在於利用軟體工程做常用的技法, 增加一層間接性, 然後將所有的相依性封裝於這層見間性上 - 其實就是所謂的 抽象界面

其實, COM, Java 或是 .NET 中的介面, 就可以做到這樣的理想, 但是, 不是只要有定義了介面, 這件事情就可以自然的發生, 界面的設計的抽象性才是成功的關鍵因素

關於上述的相依性, 可以用幾種技巧來達到 Decoupling 的效果

  • 編譯相依: Pimpl, 編譯防火牆, 減少型別的濫用, 妥善規劃 Header file 並免非必要的宣告汙染
  • 行為相依: 將 Class分解成小的單元, 將 Inheritance 轉化成 composition , 定義抽象界面
  • 模組相依: 剔除非必要引用的模組, 延遲載入 Delay load
  • 潛在相依: 利用編譯手法來剔除未定義或非必要的程式行為, 只提供有限度的自由

Dependency Inversion Principle

2/02/2009

Greedy algorithm and Greedy strategy

學過演算法的朋友們應該都學過一個有名的演算策略那就是貪婪法 Greedy algorithm, 這個演算法有容易實做, 控制狀態少, 並且效率高的幾個特點. 但是, 它往往會被誤用, 為什麼呢? 原則上貪婪法是一種動態規劃的特殊形式, 怎樣的問題才能使用動態規劃 Dynamic programming 來找出解呢? 那就是問題必須要符合 Optimal substructure 這種特性, 以下就是對這種特性有名的描述

A problem exhibits optimal substructure if an optimal solution to the problem contains optimal solutions to the sub-problems. Introduction to Algorithms (Cormen, Leiserson, Rivest, and Stein) 2001, Chapter 16 "Greedy Algorithms".

當然, 貪婪法比動態規劃的限制要多的更多, 因為只有當每次選擇的 sub-optimal solution 所組合出來的 solution 也會是整體的 optimal solution 時, 這個演算策略才有意義. 這也就是為什麼貪婪法常常會被誤用的原因, 因為它容易思考, 直覺與並且易於實踐, 但要證明該問題適用於貪婪法是相對的困難.

很多事情也是如此, 不只有在演算法這個範疇, 就以我常遇到道的軟體開發來說, 這個問題也時常出現, 許多的貪婪策略一直不斷的被誤用. 軟體發展到現行的商業競爭中, 是種跟時間賽跑的競爭模式, 因為, 似乎大家的創新跟點子都差不了太多, 能夠早點切入市場變成為關鍵性的因素. 由這樣的特質導致軟體需要快速的被開發, 但是 "快速" 這個需求就產生了截然不同的多種策略. 首先我相信軟體設計這個問題是具有 Optimal substructure 的特性, 簡單的說它是可以利用多個 sub-optimal solution 產生出最佳的 optimal solution, 但是, 有兩個盲點: 1. 所謂的最佳有很多種相度, 最短時間, 最佳的設計, 最少人力, 最安全, 最高品質, 讓使用者最上手的操作流程等等, 軟體開發的相度遠比我們想像的多 2. 即使有個綜合所有相度的衡量標準, 在每次選擇當下最佳解的策略進行發展下, 也無法得到整體性的最佳解, 何況這個衡量標準也不一定存在, 因為這些相度可能是互斥的. 基於上述的論點, Greedy strategy 在軟體開發上是不可行的, 這樣的策略下, 往往只是疊床架屋, 浮沙築高台.

每當要評估軟體開發的時程時, 最常看到的就是

Could you comment which way take less engineer effort to maintain? Please consider schedule as well.....

明顯的貪婪策略, 至於 Effort 這個字, 我想翻譯成 "麻煩" 是最恰當的, 可以見得, 發展軟體的態度與文化深深影響著軟體品質, 這也是決定整體是否正向循環的決定性因素.

2/01/2009

2009 02 01 向天池