<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>同人的生活派對 &#187; 分析設計建模</title>
	<atom:link href="http://www.lifeparty.idv.tw/blog/archives/category/%e8%bb%9f%e9%ab%94%e9%96%8b%e7%99%bc/%e5%88%86%e6%9e%90%e8%a8%ad%e8%a8%88/feed" rel="self" type="application/rss+xml" />
	<link>http://www.lifeparty.idv.tw/blog</link>
	<description>君子學以聚之,問以辨之,寬以居之,仁以行之</description>
	<lastBuildDate>Fri, 05 Mar 2010 04:18:37 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Java 泛型複雜嗎？</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/2328</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/2328#comments</comments>
		<pubDate>Wed, 16 Dec 2009 10:53:07 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[品質文化]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[學習]]></category>
		<category><![CDATA[編程技巧]]></category>
		<category><![CDATA[職場]]></category>
		<category><![CDATA[設計原則]]></category>
		<category><![CDATA[軟體開發]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/?p=2328</guid>
		<description><![CDATA[表面上看起來好像實作泛型可以讓某一段程式碼重複使用，但 Java 在泛型的限制，也增加他重構程式碼的困難度與複雜度。這麼說來，假如石頭成的想法是正確的，用 Java 的泛型來重構程式碼，只會讓程式員沒事自討苦吃。然而，同人在仔細研究他的程式碼之後，發現可以用更簡潔的方式來使用 Java 的泛型。]]></description>
			<content:encoded><![CDATA[<p>石頭成在〈<a href="http://blog.roodo.com/rocksaying/archives/10914229.html">與 metavige 和 alexchen 對話 Java 語言</a>〉這篇文章中，直言他對 Java 語言泛型的批評：</p>
<blockquote><p>我不認為 Java 讓我們「慢工出細活」，我覺得它帶來的是「冗餘的複雜性」。就算以 C++ 的觀點來看 Java 程式碼，我仍覺得 Java 程式碼有許多不必要的複雜度。Java 把類別變成施加在程序員身上的束縛，而是不是幫助我們進行抽象化資料處理的工具。</p>
<p>我個人認為，所謂「更強化的靜態型態」是跟 C++ 樣板相比， Java 泛型比起 C++ 樣板是在走回頭路。就我到目前為止的 Java 使用經驗來看，我幾乎以為泛型只是 Java 專門用來重新設計容器類別的特殊語法。在那以外的場所，你大概不會想用泛型來重構你的程式碼。</p>
<p>metavige 就說會想用 Strategy Pattern 來重構我在〈<a href="http://blog.roodo.com/rocksaying/archives/10890551.html">從 C++ Template 到 Java Generic，一步一步來</a>〉舉的例子，而不會用泛型。但是泛型難道不是用來處理這個問題的直覺想法嗎？ Java 沒有足夠理由說服我們不要用泛型來做，但是用泛型來做&#8230; 呃，似乎更困難。 </p></blockquote>
<p>石頭成用<a href="http://en.wikipedia.org/wiki/Generic_programming">泛型</a>來重構程式碼，是不是處理他的問題之直覺想法？我想石頭成比較偏好動態型態語言的自由，比較不欣賞「更強化的靜態型態」的做法。或許是因為 C++ 用 template 實作不會受到參數型別類型的限制，自然會讓他比較想用泛型來重構程式，以去除資料型態與演算邏輯的相依性。然而，看過石頭成所舉的實例，同人認為用泛型來實作的想法未必是直覺的做法。</p>
<p>表面上看起來好像實作泛型可以讓某一段程式碼重複使用，但 Java 在泛型的限制，也增加他重構程式碼的困難度與複雜度。這麼說來，假如石頭成的想法是正確的，用 Java 的泛型來重構程式碼，只會讓程式員沒事自討苦吃。然而，同人在仔細研究他的程式碼之後，發現可以用更簡潔的方式來使用 Java 的泛型。不過，還是讓我們先來看看石頭成的程式碼：</p>
<p><u>Cx.java</u></p>
<pre class="brush: java; tab-size: 2">
public class Cx&lt;DataType extends IDataType&lt;ReturnType&gt;, ReturnType&gt; {
	private DataType data;

	public Cx(DataType v) {
		data = v;
	}

	public ReturnType getData() {
		return data.value();
	}
}
</pre>
<p><u>IDataType.java</u></p>
<pre class="brush: java; tab-size: 2">
public interface IDataType&lt;ReturnType&gt; {
	     public ReturnType value();
}
</pre>
<p><u>N.java</u></p>
<pre class="brush: java; tab-size: 2">
public class N implements IDataType&lt;Integer&gt; {
	private Integer n;

	public N() {
		n = 0;
	}

	public N(Integer v) {
		n = v;
	}

	public Integer value() {
		return -n;
	}
}
</pre>
<p><u>M.java</u></p>
<pre class="brush: java; tab-size: 2">
public class M implements IDataType&lt;Integer&gt; {
	private Integer m;

	public M() {
		m = 0;
	}

	public M(Integer v) {
		m = v;
	}

	public Integer value() {
		return m * 10;
	}
}
</pre>
<p><u>S.java</u></p>
<pre class="brush: java; tab-size: 2">
public class M implements IDataType&lt;Integer&gt; {
	private Integer m;

	public M() {
		m = 0;
	}

	public M(Integer v) {
		m = v;
	}

	public Integer value() {
		return m * 10;
	}
}
</pre>
<p><u>Main.java</u></p>
<pre class="brush: java; tab-size: 2">
public class Main {
	public static void main(String[] args) {
		N n = new N(1);
		Cx&lt;N, Integer&gt; cn = new Cx&lt;N, Integer&gt;(n);
		System.out.println(cn.getData());

		M m = new M(1);
		Cx&lt;M, Integer&gt; cm = new Cx&lt;M, Integer&gt;(m);
		System.out.println(cm.getData());

		S s = new S("hello");
		Cx&lt;S, String&gt; cs = new Cx&lt;S, String&gt;(s);
		System.out.println(cs.getData());
	}
}
</pre>
<h4>問題</h4>
<p>石頭成認為他的程式存在兩個問題。第一個問題是原來 N、M、S 三個類別沒有繼承關係，但為了符合 Java 泛型的特性，在重構之後這三個類別卻實現了同一個界面。石頭成原來認為用泛型解決演算法重複問題時，應該依然保持那三個類別之間無關性，但重構之後卻發現 Java 的泛型並不能保持那三個類別的無關性。雖然在這個範例中影響不大，但他還是認為 Java 在泛型方面的表現，只能算是勉強及格。</p>
<p>第二個問題是他不知道如何在 Cx 中增加預設建構子，來初始化 data 成員變數。如果 Java 直接 new 一個參數化型別的實例，在下面的程式碼的第 5 行的地方，會出現「Unexpected type」的編譯錯誤訊息。但在 C++ 的 template 中，這種寫法卻是可以被接受的，令他實在是不能理解，JVM 為什麼不能自己反射參數化型別來建立物件實體，而要用這個限制來難倒程式員。</p>
<pre class="brush: java; tab-size: 2">
public class Cx&lt;DataType extends IDataType&lt;ReturnType&gt;, ReturnType&gt; {
  private DataType data;  

  public Cx() {
    data = new DataType();
  }  

  public Cx(DataType v) {
    data = v;
  }  

  public ReturnType getData() {
    return data.value();
  }
}
</pre>
<p>要對石頭成提出的這兩個問題有比較精確的理解，不能不對 <a href="http://en.wikipedia.org/wiki/Concept_%28generic_programming%29">concept 的概念</a>有所認識。在泛型程式設計中，concept 指的是一組型別，它們提供某些語法與語意操作的支援。concept 與抽象基礎類別有關，但 concept 並不需要子型別的關係。</p>
<p>在 C++，concept 這個術語只是簡單地命名並簡單描述參數型別的需求，而符合此 concept 的型別，一般被稱為 model。我們不能用程式語言明確地表達 concept，只能表示某種參數型別的物件會嘗試執行什麼操作，以及期待的工作結果；也就是讓它可以正常的編譯。一般來說，Java 及 C# 的泛型與 C++ 的泛型有點像，只不過它們用 interface 來扮演 concept 的角色。</p>
<p>但這樣一來，符合的參數型別就只能是實作某個明確的界面，當然 concept 顯然會比 interface 更有彈性，因為 concept 可明確代替一組型別，而不只是一個實作某 interface 的類別；而且運用隱式地定義自動 concept，可以讓參數型別同時使用內建型別或其它為了非特殊用途的型別。</p>
<h4>相依性</h4>
<p>從以上的觀念來看，用 C++ 的 template 來實作泛型程式設計，的確比 Java 的 interface 有更大的彈性。不過，即使是使用 concept 來表示參數型別，在這組支援泛型實作的型別中，其實也必須支援 concept 定義的基本操作。就廣義來說，model 必須依存於 concept 所定義的界面。相對於<a href="http://en.wikipedia.org/wiki/Liskov_substitution_principle">物件導向的 LSP</a> 原則，子類別應該可以替換父類別，concept 與 model 之間也有類似的關係。如果 Cb 是 Ca 的強化 concept；也就是定義更多延伸 concept 所需的操作，那麼 Cb 的 model 則必然是 Ca 的 model。</p>
<p>於是，我們看到石頭成的程式，N、M、S 都實作相同的界面 IDataType，這將不會是個問題。其實改動 N，並不會影響到 M、或是 S，因為三者並不直接相依，除非它們共同引用的界面變動才會影響到其它實作相同界面的程式。事實上，界面完全不牽涉到任何實作，除非界面定義得不夠完整，石頭成所擔心的問題才會發生。但如果是這樣，那將不是程式語言的問題，而是設計本身的問題。</p>
<h4>複雜性</h4>
<p>當然，我們也不能否認石頭成提到的另一個問題，是實際存在的事實。Java 使用 interface 來實作泛型確實有比 C++ 的型別 concept 有不足之處。以 interface 來抽象化參數型別沒辦法做到 default construction 這種型別 concept，因為它不是類別，沒有辦法自己生成物件實體，必須要靠外界呼叫者初始化參數型別物件，再傳給泛型物件才能正常工作。</p>
<p>再加上 Java 無法在執行期的時候取得參數型別的關係，我們無法直接以反射機制產生出參數型別的建構式來產生物件實體。雖然繞個圈，我們還是可以用另一種反射方式達到目的，但這樣會使得程式碼變得更複雜而且難以理解，實在是會令人望之卻步。那麼使用 Java 泛型，有沒有比較簡單的方法可以解決這個問題呢？</p>
<p><u>C++</u></p>
<pre class="brush: c++;">
DataType data; // default construction
</pre>
<p><u>Java</u></p>
<pre class="brush: java;">
//data = new DataType();
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
data = ((Class) pt.getActualTypeArguments()[0]).newInstance();
</pre>
<h4>解決方法</h4>
<p>其實解決的方法並不難，雖然我們在 Cx 仍然很難用參數型別反射預設建構式來建立物件實體，但如果你對同人過去發表的〈<a href="http://www.lifeparty.idv.tw/blog/archives/449">降低資料存取的複雜性</a>〉還有印象的話，你可以從這篇文章找到解決石頭成問題的方向。我們可以在這篇文章看到，在還沒使用 Java 的泛型之前，UserDataAccessorImpl 有預設建構子可供初始化 UserDataAccessor 的物件實體，但使用 Java 的泛型重構之後，泛型版本的 HibernateDataAccessor 卻只提供指定參數型別 BizObject 的 Class 建構式。所以使用同樣的原理，我們應該也可以解決石頭成的第二個問題。</p>
<pre class="brush: java; tab-size=2">
	public Cx(Class&lt;? extends IDataType&lt;ReturnType&gt; &gt; dataTypeClazz) throws Exception {
		data = dataTypeClazz.newInstance();
	}
</pre>
<p>或許有人可能會說我改變了石頭成第二個問題的需求，他要的不是以 DataType 的 Class 當參數的建構式，而是不需要任何參數的預設建構式。但其實希望在 Cx 加入預設建構式並不是需求，而是為了解決問題的需求所做的假設，要認清需求我們必須質疑存在這個假設背後的問題。</p>
<p>當我們想到 Cx 為什麼需要預設建構式的時候會發現，有些時候因為要<a href="http://www.lifeparty.idv.tw/blog/archives/271">跨越遠端界面呼叫的藩離</a>，可能沒辦法由呼叫者產生參數型別的物件傳給 Cx，而要由 Cx 自己初始參數型別物件。但通常傳入參數型別物件的 Class 是可行的，因為運用 Java 的反射機制，傳遞 Class 就好像傳遞 String 一樣簡單。</p>
<h4>更直覺的做法</h4>
<p>寫到這裡，石頭成的問題應該是解決了。不過回到這篇文章同人一開始所提到的，石頭成所舉的實例，我認為用泛型來實作的想法未必是直覺的做法。那麼同人認為更直覺的做法是什麼？其實我不會完全用泛型來解決石頭成的問題，而是運用多型來封裝演算法的差異性，再搭配泛型來強化參數型別的一致性。這樣我只需要修改石頭成的 Cx 類別與 Main 主程式碼，如下面這段程式碼所示，同人認為是更為簡單而直覺的做法。</p>
<p><u>Cx.java</u></p>
<pre class="brush: java; tab-size: 2">
public class Cx&lt;ReturnType&gt; {
	IDataType&lt;ReturnType&gt; data;

	public Cx(Class&lt;? extends IDataType&lt;ReturnType&gt; &gt; dataTypeClazz) throws Exception {
		data = dataTypeClazz.newInstance();
	}

	public Cx(IDataType&lt;ReturnType&gt; data) {
		this.data = data;
	}

	public ReturnType getData() {
		return data.value();
	}
}
</pre>
<p><u>Main.java</u></p>
<pre class="brush: java; tab-size: 2">
public class Main {
	public static void main(String[] args) {
		N n = new N(1);
		Cx&lt;Integer&gt; cn = new Cx&lt;Integer&gt;(n);
		System.out.println(cn.getData());

		M m = new M(1);
		Cx&lt;Integer&gt; cm = new Cx&lt;Integer&gt;(m);
		System.out.println(cm.getData());

		S s = new S("hello");
		Cx&lt;String&gt; cs = new Cx&lt;String&gt;(s);
		System.out.println(cs.getData());
	}
}
</pre>
<p>上面這一段程式碼，將類別 Cx 當中的 data 的型別，由 DataType 參數型別改成 IDataType 的多型界面。這樣一來，data 的抽象概念就從參數型別轉移到多型界面，此界面封裝了不同的資料型態行為的抽象概念。如此一來，Cx 可減少了一個參數型別，程式碼也變得簡明易懂。從 Main 呼叫端的程式碼就可以發現，少了一個參數型別，程式碼也變得簡潔許多。</p>
<p>這段程式碼也讓我們發現泛型可以強化多型界面的資料型別。假如沒有泛型，我們就只能在 DataType 的 value() 回傳值定義成可以包容各種資料的型別；例如 String 或 Object，然後再運用資料轉換或強制轉型的方式取得我們需要的資料格式，但這些方法很容易出錯，而且會造成程式開發不小的負擔。當我們運用泛型來強化多型界面的型別，程式員就不用在這方面傷腦筋了，而且設計的完整概念也更容易維持。</p>
<h4>泛型與多型的差別</h4>
<p>就設計的觀點來看，參數型別並不是真正的實體型別，如果沒有 default construction 的 concept，我們沒辦法拿它來產生任何實體型別的實體。多型的解決方案，是運用型別來抽象化型別，與泛型用型別的 concept 來抽象化型別是不同的抽象觀點。後者如果想在 Java 的泛型來取代多型的解法，其實是會讓程式員遇到很多麻煩的。</p>
<p>其實即使換成 C++，同人不會把不同資料型態以 DataType 參數型別抽象化。雖然 C++ 的 template 真的很有彈性，但那並不意味著使用它不需要為這些彈性付出代價。例如，使用 C++ template 編程，在除錯上或處理編譯的錯誤會比沒有使用 template 還要困難許多。</p>
<p>尤其是 C++ template 的編輯錯誤訊息，常讓人搞不清楚錯誤在那裡，往往要花費很大的工夫才找到程式員無心犯下微不足道的錯誤。所以， C++ 的 template 給予程式員相當大的彈性，但過度地使用它，也很容易讓程式碼產生不必要的複雜度。</p>
<p>依同人的經驗顯示，許多表面上泛型似乎可以用來解決的多型問題，但一旦用泛型來解，常會因為抽象化層次不同的隔閡而使問題更為複雜。</p>
<p>泛型的原理是引用 concept 來代表各種可供參數化的型別，而多型則是使用抽象類別或界面來封裝具體類別。兩者的抽象化的觀點不一樣，一種是型別的集合對個別型別的關係，另一種則是型別與型別的關係。</p>
<p>前者的關係我們稱為某些型別是某個 concept 的 model，而後者的關係則是型別之間的一般化或實現關係。兩者在本質上的概念是不一樣的，因此當我們想用 concept 來代替 <a href="http://en.wikipedia.org/wiki/Inheritance_%28computer_science%29">inheritance</a>，或是用 inheritance 來取代 concept，最後都必然會讓我們遇到實作與概念上的隔閡。</p>
<p>當一個類別 A 使用到泛型類別 B 時，我們就必須指明使用 B 所需要的參數型別，要不然就必須由使用到類別 A 的類別 C 指定需要的參數型別。所以如果類別 A 想要不侷限在某種資料型別，那麼使用泛型類別 B 的代價就必須是讓類別 A 也變成泛型物件。所以大量使用泛型的結果，會使你一層又一層地使用參數型別，這當然是比多型複雜許多。</p>
<p>此外，以泛型來取代多型更麻煩的是，不同參數型別的泛型物件是完全不同的類別，它們不像多型可以用同樣的方式來處理不同資料型態。所以雖然表面上泛型省去繼承類別或實作界面的工作，看起來比多型的解決方法有更大的自由度。但這也意味著泛型可能會缺少對問題語意封裝抽象概念，只是站在實作觀點以一致性的方法來滿足需求。</p>
<p>程式員不該為了貪圖一致性而忽略多樣性，而是應該按照實際的需要來解決問題。所以重點不是 Java 泛型是否複雜，而是我們能不能運用設計使它變得更簡單。語言的限制未必是加諸在程式員身上的束縛，而可能是讓我們進行思考以發揮設計創意的機會呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/2328/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>強迫新手這麼做的風險</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/1281</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/1281#comments</comments>
		<pubDate>Fri, 07 Aug 2009 09:26:26 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[利害關係人]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[寫作]]></category>
		<category><![CDATA[專案風險]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[溝通]]></category>
		<category><![CDATA[生活感觸]]></category>
		<category><![CDATA[組織]]></category>
		<category><![CDATA[職場]]></category>
		<category><![CDATA[領導]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/?p=1281</guid>
		<description><![CDATA[同人看 Kenming Wang 這篇文章覺得怪怪的，倒不是不贊同他對寫好使用案例好處的觀點，而是覺得強迫新手去做我們認為有價值的東西是很危險的。 ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.kenming.idv.tw/">Kenming Wang</a> 在〈<a href="http://www.kenming.idv.tw/what_is_the_befenifs_of_the_use_case">寫好使用案例 (Use Case) 有什麼好處？</a>〉中提到寫好使用案例的好處。文章提到有位其中一位較為資深的程式開發人員在他在工研院授課時表示感覺不到寫好使用案例有什麼好處。這問題讓他思考許久後回答，他認為寫好使用案例最直接的關鍵是，影響整個專案開發流程的節奏。</p>
<p>這篇文章分享他對寫好使用案例對專案好處的看法，他總結使用案例的好處是族繁不及備載。並提到越大規模的專案，更能感受到開發節奏的順暢度。再加上 『漸進循環 (incremental and iteration)』 的開發模式，會越形容易謀和在系統開發期間，人與事的種種。</p>
<p>不過，Kenming Wang 在文章最後提到以上的論述不能說服那位程式開發人員，因為程式設計人員多半以局部或個別的角度來看系統開發，所以使用案例寫得好不好，對他們沒差。只有像專案經理或軟體架構師以專案整個全局來看時，才會有明顯的感受。</p>
<p>但他認為不需要去說服那位程式開發人員，並引述 Martin Fowler 在《UML Distilled》一書中曾經說過的：「你只能強迫新手們這麼做。過了幾年後，他們會突然恍然大悟，然後腦袋彷彿重生！」這句話來說明他對這位程式開發人員意見的看法。</p>
<p>同人看 Kenming Wang 這篇文章覺得怪怪的，倒不是不贊同他對寫好使用案例好處的觀點，而是覺得強迫新手去做我們認為有價值的東西是很危險的。</p>
<p>因為這些主觀價值如果不能以客觀的方式來表達甚至衡量，這些只會造成實際執行開發工作的人的困擾，不知道所謂的「好」或「對」的作法，到底與他們的工作有什麼直接關係，如此只會使他們工作變得更複雜。如果未來真能讓他們恍然大悟而腦袋重生倒也還好，但如果最後發現期望落空，是否只是浪費開發工作者寶貴的時間和精力呢？</p>
<p><a href="http://www.anobii.com/books/UML精華第三版/9789861540399/000a9259120e772e7a/" title="More about UML精華第三版"><img src="http://image.anobii.com/anobi/image_book.php?type=4&#038;item_id=000a9259120e772e7a&#038;time=0" title="More about UML精華第三版" align=right alt="More about UML精華第三版" style="padding: 5px;" /></a>尤其同人很懷疑 Martin Fowler 會說「你只能強迫新手們這麼做」的話。當然，同人對「腦袋重生」有印象，但記得不是出現在使用案例的章節，而是出現在循序圖的章節中，提到集中式或分散式物件設計的不同思維。果然，同人回家翻閱譯者光正兄送我的《<a href="http://www.anobii.com/books/UML精華第三版/9789861540399/000a9259120e772e7a/" title="More about UML精華第三版">UML精華第三版</a>》發現，在循序圖的章節有下面這一段文字。</p>
<blockquote><p>這種設計風格的改變正是物件導向設計典範革命的核心所在。不過，這也是難教導之處。真正了解物件導向典範唯一方式似乎就是在強烈使用分散設計風格的OO環境工作一陣子。許多人會突然恍然大悟，開始理解到這種設計風格究竟為何。這時候，他們的腦袋將重獲新生，也開始比較容易思考分散控制的設計風格。（趙光正譯，2004，《UML 精華第三版》，「Chapter 4 循序圖」，p4-6）</p></blockquote>
<p>同人知道 Kenming Wang 是很聰明的顧問，懂得舉一反三引用大師的觀點來強化自己的論點。但用在此處看來有斷章取義之嫌，而且非常危險，我認為這很容易淪為方法論的主觀價值批判。因為不管怎麼說，如果光正兄的翻譯沒有錯誤的話，文句的原意並沒有貶抑不同開發觀點的意味。</p>
<p>同人以為問題的關鍵並不在某種開發典範有何好處，所以應該強迫他們採用、適應、進而熟練方法進而讓專案獲益。而是在於強迫導入任何方法論都會存在風險，我們是否能夠充分理解這些風險對專案產生的影響，以及所付出的代價是否值得。或許你還會有印象，同人在〈<a href="http://www.lifeparty.idv.tw/blog/archives/982">簡單，複雜世界的致勝之道</a>〉分享過複雜面向的最主要來源是變革欠缺整合。</p>
<blockquote><p>高層主管與員工對整合觀點的懸殊態度，對於整合，領導者關心的焦點是管理、控制與協調的工具；而員工關切的重點是有利個人決策的工具，執行工作者的觀點常會受到不平等的對待。</p></blockquote>
<p>從這裡我們可以很輕易地了解，為什麼很多使用案例方法的導入常使軟體開發人員的工作變得更複雜。可能你會說那是因為誤用使用案例，或是沒有寫好使用案例，但其實依據同人的觀察，造成誤用或使用案例的不良寫作多半是因為不同觀點的差異，進而導致彼此的溝通不良所致。</p>
<p>站在全局觀點的人總是認為沒問題，但實際上他們並不能提供局部或個別觀點足夠的資訊，以利其進行開發上的決策。而往往為了符合全局觀點的架構上的框架，往往要逼著程式開發人員削足以適履。</p>
<p>請不要誤會，同人並非否定寫好使用案例的好處。事實上，從我過去的工作經驗，我很能體會寫好使用案例的好處，只不過我從來不認為好的使用案例可以解決不同觀點的整合問題；其實沒有任何的文件可以做到這點，除非允許各種聲音充分表達，然後進行相互對話以提昇溝通品質。</p>
<p>為什麼沒有任何文件可以解決不同觀點的整合問題呢？因為不管文件所傳達的觀念多麼「好」或是「正確」，而沒有與其他的觀點進行互動與溝通，進而分享意義，很容易讓各種不同的觀點各說各話，而無法促成彼此的思考與反省，進而激發出可以解決問題的智慧。所謂的「好」或是「正確」只是基於已知最佳實務的記憶，而不是為了解決複雜問題的思考，因此不見得可以有效地因應問題反而使問題更加複雜化。</p>
<p>不同觀點的整合，這絕不是「強迫新手這麼做」就可以成功的。你會需要實際執行工作者的回饋，了解他們的問題與期望，才能幫他們更簡單的完成工作，才不會因為忽略程式開發人員觀點的思慮不周，使工作變得更複雜而浪費他們的時間與心力。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/1281/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>結構與非結構的隔閡－從軟體開發專案的四個困難談起</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/888</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/888#comments</comments>
		<pubDate>Mon, 06 Jul 2009 01:17:57 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[CNet/ZDNet]]></category>
		<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[利害關係人]]></category>
		<category><![CDATA[寫作]]></category>
		<category><![CDATA[專案團隊]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[溝通]]></category>
		<category><![CDATA[生活感觸]]></category>
		<category><![CDATA[知識管理]]></category>
		<category><![CDATA[職場]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/?p=888</guid>
		<description><![CDATA[系統分析師該如何思考與學習的方法以展現其專業。然而，許多人對系統分析專業的疑惑出在忽略「結構與非結構的隔閡」，使得系統分析師陷入了過度簡化設計與過度工程化，也就是所謂過度設計的兩難情境。]]></description>
			<content:encoded><![CDATA[<p>這篇文章是投稿 <a href="http://www.zdnet.com.tw/">ZDNet Taiwan</a> 的文章原稿，由 ZDNet Taiwan 以〈<a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20138690,00.htm">軟體開發的難處 SA該如何解決？</a>〉、〈<a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20138901,00.htm">為何SA很難落實簡單設計</a>〉兩篇文章刊登。文章原稿未經 ZDNet Taiwan 編輯，內容可能與 ZDNet Taiwan 約略有所不同。</p>
<p>今(09)年初，應中山大學資管系主任鄭炳強教授的邀請，到他們學校做了一場演講。由於筆者與鄭教授原先並不認識，是透過台科大資管系主任李國光教授聯絡到筆者，因此，鄭教授邀請我在演講前先與他碰面、共進午餐，並且藉這個機會交流彼此在軟體工程方面的心得。</p>
<p>在那次午餐約會中，我們聊到了系統分析專業這個議題。鄭教授表示欣賞筆者寫的〈<a href="http://www.lifeparty.idv.tw/blog/archives/349">展現系統分析專業的七種能力</a>〉，還曾在課堂上向他的學生推薦這篇文章…與鄭教授交流互動的過程中，也讓筆者得到不少收穫，回到台北後，一直想找機會分享這些收穫。</p>
<p>由於我一直想找機會回應那篇文章的讀者意見，也就是ZDNet讀者對於那篇文章的前半段<a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20129995,00.htm">〈怎樣才是專業的 SA？〉的一些留言</a>，筆者發現這次行程的收穫，正好可以讓這篇文章有一個很好的起點。</p>
<h4>軟體專案開發的四個困難</h4>
<p>在言談之間，筆者可以感受到鄭炳強教授對台灣軟體產業發展很關心，但他對一般軟體從業人員忽略軟體工程的基本修煉卻很憂心。</p>
<p>他觀察到人們往往熱衷於追求新技術，而總是忽視軟體工程的基本原理。他還指出軟體開發與一般產品開發有著一個根本上的不同；也就是知道開發方法還不夠，更必須了解方法運作背後的原理。因為不了解原理就不能針對問題進行正確的分析與設計，更不用說可以有辦法順利地解決問題。</p>
<p>這也就是軟體專案比其它專案還要困難的地方，他認為軟體專案開發主要有四大困難，也就是溝通的困難、問題本質的困難、整合的困難、以及團隊合作的困難。後來筆者在他寫的書中看到更為清楚的對照；亦即「電腦對人腦、答案對問題、程式對系統、個人對團隊」。</p>
<p><a href="http://www.anobii.com/books/管理資訊系統/9789574830497/01ec093ad712a748d1/" title="More about 管理資訊系統"><img src="http://image.anobii.com/anobi/image_book.php?type=3&#038;item_id=01ec093ad712a748d1&#038;time=1224086548" title="More about 管理資訊系統" alt="More about 管理資訊系統" style="padding: 5px;" align=left /></a>站在資訊系統的企業觀點來看，資訊系統是企業為了因應環境挑戰而發展出來的解決方案<sup>[1]</sup>，所以系統分析師必須找到可以解決真實世界的問題的解決方案，這是屬於解決方案的結構化範疇。然而，這意味著系統分析師必須比系統使用者更了解他們的問題，這些問題多半是半結構化，甚至是非結構化的，因此困難的是如何讓結構化的解決方案領域、與非結構化的問題領域進行溝通。</p>
<p>因此，建置一個可解決使用者需求的資訊系統，系統分析師必須要能發現藏在需求背後的真正問題，否則開發出來的系統往往會很難解決系統使用者的問題。正因為如此，系統分析師不能只考慮到技術層面，也不能把問題只是簡化成系統使用者所提及需要的功能，而必須將它們放在一起，統合思考以形成能夠相互協調的系統。如果想要達到上述目標，光靠個人單打獨鬥當然不夠，而是必須藉由團隊合作的力量。</p>
<p><img src="http://www.lifeparty.idv.tw/blog/wp-content/uploads/2009/07/070609_0117_1.png" alt=""/><br />
圖1：問題領域與解決方案領域</p>
<h4>該相信誰的專業？</h4>
<p>所以，從軟體專案開發主要的四大困難的觀點來看，我們就能輕易瞭解專案成敗的關鍵真的不是 know how，而是在 know why。</p>
<p>這從〈怎樣才是專案的 SA？〉的回應中也可以看到。系統分析專業並不在於使用什麼開發方法，而是在於當開發方法碰到了阻礙或挫折時該怎麼辦？如果系統分析師沒有問為什麼的能力，不去弄清楚 know why，將很難克服上述的阻礙或挫折，使他們所熟悉的理論及方法可能無助於解決實際碰上的難題。</p>
<p>例如有位一路走來的 SA，留言提到他很怕遇到一種人，這種人會主張把系統設計得簡單點，但大多數卻習慣先把使用者需求簡化或忽略困難的部分。結果使得系統在後面的開發變得愈來愈困難，或是使得系統效率不彰。</p>
<p>還有一位訪客提到，台灣中小企業老闆普遍的觀念是「資訊系統應該是要配合他的需求而開發，而不是為了配合系統來改變公司」三不五時會表現出他們的官大學問大。遇到這些情境，系統分析師該相信誰的專業呢？</p>
<p>筆者相信以上是許多系統分析師經常碰到的問題。在軟體開發過程中，不同角色的意見常常是分歧的。如果系統分析師無法適時、有效地處理這些衝突，根本就很難施展出可以解決問題的專業。那麼系統分析師該如何有效處理軟體開發過程不同角色的歧見所產生的衝突呢？筆者認為解決衝突的關鍵不在系統分析師的設計才華、或是技術能力如何，也不在他所懂的領域知識有多少。</p>
<p>雖然這些能力確實在軟體開發過程中非常重要，但如果忽略了結構與非結構的隔閡，那麼即使擁有上述才華、能力與知識還是沒有辦法把心思放在對的問題上，而無法發展出適當的解決方案。</p>
<p>筆者曾在〈<a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20129997,00.htm">系統分析專業的七種能力</a>〉提出系統分析師該如何思考與學習的方法以展現其專業。然而，從〈<a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20129995,00.htm">怎樣才是專業的 SA？</a>〉的留言卻可以發現，許多人對系統分析專業的疑惑出在忽略「結構與非結構的隔閡」，使得系統分析師陷入了過度<strong>簡化設計</strong>與過度工程化，也就是所謂<strong>過度設計</strong>的兩難情境。</p>
<h4>簡單設計並不容易</h4>
<p>在觀念上，很多人都知道要把系統設計得簡單點，但實務上設計要做得簡單卻非易事。誠如那位一路走來的 SA 讀者所言的，許多主張把系統設計得簡單點的想法，最後多半變成簡化使用者需求或忽略困難的部分，使得後續開發或系統效能遭遇到瓶頸。</p>
<p>筆者很能夠體會他對主張把系統設計得簡單點的恐懼，事實上，筆者經常看見許多一開始強調設計簡單，到最後卻因為沒辦法適應變化而得修改或重寫，如果上述改變又牽動到系統架構，那更是使得問題變得更複雜。由此可知，簡單設計並不容易，簡化使用者需求或是忽略困難部分的設計，不能算是簡單的設計，而是過度簡化的設計。</p>
<p>筆者認為簡單設計代表設計的簡明與單純，簡明是指設計概念清晰，使人容易理解，同時也是讓系統分析師用來發現，有效解決問題的一致性概念。至於單純則是採用直接而純粹的實作，以避免不必要的複雜度，集中心力解決最重要的問題，不把時間浪費在無關緊要的事情上。</p>
<p>只有做到設計的簡明與單純，才不會因為無法善用設計的彈性來突破系統的限制，或是為了沒有必要的彈性而增添無謂的複雜度，否則將會使開發過程碰到困難甚至是失去控制。<strong>簡明和單純就如同天平的兩端，讓問題領域的重視變化與解決方案領域的強調秩序能夠相互激發出智慧的火花，形成穩定的動態平衡，而不是讓一端牽就另一端</strong>。</p>
<p>很多系統分析師習慣地以「做的事情很簡單」視作簡單設計的認定標準，大概是因為基於設計解決方案的思考慣性，加上受到「簡單」的刻板印象。</p>
<p>殊不知簡單設計必須以解決問題為前提，忽略或過度簡化問題所做的設計，通常是無法滿足問題領域的現實需要。這種迷思特別是在導入新技術或開發方法時更容易看見。以為新工具或方法會讓開發過程變得更簡單而更有效率，結果反而卻為了遷就新技術或方法，使簡單的問題複雜化。</p>
<p>其實筆者並不是要否定新技術的價值，只是認為簡單設計的關鍵並不在技術、工具或是方法論，而是更需要思考與實踐的紀律，用來跨越結構與非結構的隔閡。透過思考，系統分析師才能弄清楚系統的最主要問題，知道如何將設計變得更簡單；而唯有實踐，才能驗證自己的想法是否正確而且能對解決問題產生效果，以力圖設計的完善。</p>
<h4>「本質」的誘惑</h4>
<p>雖然說系統分析師需要紀律以思考問題、以及實踐解決方案，但實際上要做到真的很不容易。筆者從實務的觀察中發現，很多系統分析師在設計過程中，都很容易受到本質的誘惑而更加深了結構與非結構的隔閡。</p>
<p>所謂的本質是指不管環境如何改變，但仍然會有不受環境變化衝擊的觀念或方法。筆者並非否定設計本質的價值，只是覺得「本質」這個詞很容易讓人們陷入迷惘。設計能力愈強、或是經驗愈豐害的人，愈容易受到本質的誘惑而迷失方向；一旦你愈堅持你所相信的設計本質，你就會愈容易忽視思考問題的存在。</p>
<p>在軟體設計社群裡，「本質」是個很容易被濫用的名詞，筆者認為系統分析師應該要謹慎地看待這個名詞，以免受到它的誤導而弄錯問題。筆者曾經在 plurk 看到生魚片提到，從 OO 的本質下手的<a href="http://www.plurk.com/p/86k25">心得</a>，指出搭配重構與設計樣式再行體會，讓他更認識 OO 是什麼。我當時則提醒他當心設計本質很容易讓人弄錯真正存在的問題。</p>
<p>對於我回應生魚片的看法，cloudy 提出他的觀點。他認為設計本質是不會變的，只是在不同問題領域中，設計概念的資料與行為會有所增減。筆者倒是認為問題的存在會決定事物本質的不同，例如訂貨系統中的車子、與租車系統中的車子，在設計上是屬於完全不相同的概念。前者是達成交易的商品，而後者則是用來提供服務以收取租金的生財器具，真正販售的商品是租車服務而非車子本身。</p>
<p>如果同樣以「交易為中心」的設計模型，都存在這種本質的差異，那麼對於其它無法用交易解決的問題領域，更是難以讓系統分析師找到不會因為環境改變而受到影響的設計本質。因為，當存在的問題不同之時，對相同的事物會產生完全不同的意義。換句話說，設計本質並非固定不變的，而是因應系統所要解決的問題而改變。</p>
<p>其實，筆者也很難避免受到本質的誘惑，以自己過去開發過的銀行影像系統為例，一開始按照自己設計的經驗來建立設計模型，很自然地會將資料進行正規化的處理，對影像文件擷取交易的設計觀點。</p>
<p>但問題是這個系統與以往的專案最大的不同是，它並不需要處理交易的部分，而是由工作流程系統處理交易完成後，再通知影像系統以進行影像資料的存取。隨著使用者需求的變化，調整功能時卻發現交易的設計反而讓問題變得很複雜。這時才發現，以交易為主的設計本質並不適用於這個系統，而是重點在於如何讓使用者建立查詢檢索條件，方便讓他們找到需要的資料。</p>
<p>交易在此系統並不代表交易事件實際的發生（有沒有發生對此系統並不重要），而只是代表影像查詢或檢索的某一種條件限制而已。由此可知，想要找到對系統真正有用的設計觀點，並非針對事物的真實情況（本質）來建模，而是因應事物在問題領域中所表現的價值或意義（存在）來建模。</p>
<p>筆者認為，系統分析師應抱持開放的心胸，體認到軟體設計本質的未定論；存在並非由固定不變的本質來所彰顯，而是藉由創造本質的過程來體驗問題的存在，設計其實是「本來無一物，何須染塵埃」。</p>
<h4>學而不思則惘，思而不學則殆</h4>
<p>開發本質的不同常會導致設計爭論，例如強調以資料與程序為本質的論點，經常會批評用物件導向開發的設計典範。主要批評物件導向要寫更多的程式難以管理、以及開發出來的系統運作效率太差等弊病。</p>
<p><a href="http://www.anobii.com/books/黑天鵝效應/9789862130568/0137be7d8e8d6f8f46/" title="More about 黑天鵝效應"><img src="http://image.anobii.com/anobi/image_book.php?type=4&#038;item_id=0137be7d8e8d6f8f46&#038;time=1209400730" align=left title="More about 黑天鵝效應" alt="More about 黑天鵝效應" style="padding: 5px;" /></a>當然，某些以物件導向開發的系統確實會出現以上的問題，但如果改成程序導向的開發方法就沒有問題了嗎？顯然這樣的想法是忽略了「沉默的證據」<sup>[2]</sup>之存在，沒有人用不同的開發方法開發同一個系統，所以我們很難確知在某一個專案裡，用程序導向開發是否不會出現更為棘手的問題。</p>
<p>從相反的角度去思考，強調物件封裝、抽象化、繼承就是軟體設計的本質嗎？這些原則是為了降低複雜度，增加元件的彈性與再用性而產生的。不過，<strong>如果這些設計原則找不到具體可以解決問題的實踐方式，那它們就毫無用處</strong>，只能代表系統分析師還體會不到設計的本質；這個時候，他想解決的問題多半並不是系統真正的問題，所以未來必將付出為了沒有必要的彈性而增加複雜度、以及系統效率不彰等代價。</p>
<p>由此可知，<strong>設計典範沒有優劣的問題，我們也很難找到可以因應在各種狀況下最棒的設計，只有是否正視問題而發展出適合的設計</strong>。</p>
<p><a href="http://www.zdnet.com.tw/enterprise/column/softwaredev/0,2000087962,20123212,00.htm">沒有放諸四海皆準的開發方法</a>，所以系統分析師不該以為他相信的設計本質可以解決所有問題，而是應該開放自己的心胸，停下來思考自己可能忽略的問題，並且與跨領域的知識進行交流與學習，以期將所知學及所知進行組合與內化。</p>
<p>如此一來，代表結構與非結構的兩種領域，將不會因為扞格而產生格格不入的衝突。總之，系統分析師應該調和問題領域的知識與技術領域的應用，使其達成穩定的動態平衡，再加上「系統分析專業的七種能力」，那麼系統分析的工作必然會勝任愉快。</p>
附註：
&nbsp;<hr/><ol class="footnotes"><li id="footnote_0_888" class="footnote">周宣光譯，2000，《<a href="http://http://www.anobii.com/books/%E7%AE%A1%E7%90%86%E8%B3%87%E8%A8%8A%E7%B3%BB%E7%B5%B1/9789574830497/01ec093ad712a748d1/">管理資訊系統－網路化企業中的組織與科技</a>》，東華書局。</li><li id="footnote_1_888" class="footnote">林茂昌譯，2008，《<a href="http://www.anobii.com/books/%E9%BB%91%E5%A4%A9%E9%B5%9D%E6%95%88%E6%87%89/9789862130568/0137be7d8e8d6f8f46/">黑天鵝事件</a>》，大塊文化。</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/888/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>降低資料存取的重覆性</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/449</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/449#comments</comments>
		<pubDate>Sun, 07 Jun 2009 05:20:10 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[學習]]></category>
		<category><![CDATA[編程技巧]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/?p=449</guid>
		<description><![CDATA[程式碼的重覆性使程式不容易維護、以及增加系統出錯的機率，同時使得程式的再用性難以提昇。因此降低程式碼的重覆性對程式碼的品質與彈性有很大的助益。然而，在資料存取與與物件型別的無法分離的情況下，卻會使得開發者難以對治程式碼的重覆性；相同處理資料的邏輯，受限於處理資料的不同型態，使得開發者必須依據資料型態不同，而改寫同一段程式碼，使得程式碼很難共用。
就像一個比較常見的例子是資料永續存取的實作，常會因為存取資料型態的不同，而影響到資料存取的界面，因此對每一個需要持久化的商業領域物件，都必須重覆地實作它們的 CRUD 存取功能。
雖然透過永續層的概念，我們可以將這種重覆性隔離開來，加上有些 ORM 永續框架的協助，開發資料存取物件的負擔並不太大。然而即使如此，我們有沒有可能避免重覆開發資料存取的元件，不用針對每一種物件型別都要寫一支相對的資料存取物件呢？要達到這樣的目的，我們必須分離資料存取的處理程序與物件型別，但要如何才能做到呢？
假設我們要開發一個會員註冊的功能，如果考慮以測試來驅動開發過程，我們可能會先撰寫下面的測試程式：

public class MemberTester
    extends TestCase {
    private static final String MEMBER_SERVICE =
        "memberSvc";
    private ApplicationContext context = null;

    MemberTester() {
	context = new ClassPathXmlApplicationContext(
          [...]]]></description>
			<content:encoded><![CDATA[<p>程式碼的重覆性使程式不容易維護、以及增加系統出錯的機率，同時使得程式的再用性難以提昇。因此降低程式碼的重覆性對程式碼的品質與彈性有很大的助益。然而，在資料存取與與物件型別的無法分離的情況下，卻會使得開發者難以對治程式碼的重覆性；相同處理資料的邏輯，受限於處理資料的不同型態，使得開發者必須依據資料型態不同，而改寫同一段程式碼，使得程式碼很難共用。</p>
<p>就像一個比較常見的例子是資料永續存取的實作，常會因為存取資料型態的不同，而影響到資料存取的界面，因此對每一個需要持久化的商業領域物件，都必須重覆地實作它們的 <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> 存取功能。</p>
<p>雖然透過永續層的概念，我們可以將這種重覆性隔離開來，加上有些 <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORM</a> 永續框架的協助，開發資料存取物件的負擔並不太大。然而即使如此，我們有沒有可能避免重覆開發資料存取的元件，不用針對每一種物件型別都要寫一支相對的資料存取物件呢？要達到這樣的目的，我們必須分離資料存取的處理程序與物件型別，但要如何才能做到呢？</p>
<p>假設我們要開發一個會員註冊的功能，如果考慮以測試來驅動開發過程，我們可能會先撰寫下面的測試程式：</p>
<pre class="brush: java">
public class MemberTester
    extends TestCase {
    private static final String MEMBER_SERVICE =
        "memberSvc";
    private ApplicationContext context = null;

    MemberTester() {
	context = new ClassPathXmlApplicationContext(
            "conf/appContext.xml");
    }

    void testRegisterMember() {
        MemberService memberSvc = (MemberService)
            context.getBean(MEMBER_SERVICE);
        User member = new User();

        member.setId("myId");
        member.setEncodedPassword("abc");
        member.setEmailAddress("myId@domain");

        memberSvc.RegisterMember(member);

        CriteriaExpression criteria =
            Comparison.eq("id",
            ConstantExpression.getString("myId"));
        ParameterContext parameterContext =
            new HashtableParameterContext();
        User members[] =
            memberSvc.getMembers(criteria,
            parameterContext, 0);

        assertEquals(members.size(), 1);

        for (int i = 0; i < members.length();
            i ++) {
            assertEquals(member.getId(),
                members[i].getId());
            assertEquals(
                member.getEncodedPassword(),
                members[i].getEncodedPassword());
            assertEquals(
                member.getEmailAddress(),
                members[i].getEmailAddress());
        }

        memberSvc.removeMember(member);
    }
}
</pre>
<p>從測試程式找到應用服務的界面 <em>MemberService</em> ，並進一步實現符合此界面的服務元件：</p>
<pre class="brush: java">
public interface MemberService {
    public User [] GetMembers(
        CriteriaExpression criteria,
        ParameterContext context, int maxCount);
    public void RegisterMember(User member);
    public void UpdateMemberInfo(User member);
    public void removeMember(User member);
}
</pre>
<pre class="brush: java">
public class MemberServiceImpl
    implements MemberService {
    private UserDataAccessor
        memberDataAccessor = null;

    public User [] GetMembers(
        CriteriaExpression criteria,
        ParameterContext context,
        int maxCount) {
        return memberDataAccessor.find(criteria,
            context, maxCount);
    }

    public void RegisterMember(User member) {
        memberDataAccessor.insert(member);
    }

    public void UpdateMemberInfo(User member) {
        memberDataAccessor.update(member,
            LockMode.UPGRADE);
    }

    public void removeMember(User member) {
        memberDataAccessor.remove(member,
            LockMode.UPGRADE);
    }

    public void setMemberDataAccessor(
        UserDataAccessor memberDataAccessor) {
        this.memberDataAccessor =
            memberDataAccessor;
    }

    public UserDataAccessor
        getMemberDataAccessor() {
        return memberDataAccessor;
    }

}
</pre>
<p>實作服務元件會使用到界面 <em>UserDataAccessor</em> 來存取會員資料，利用永續框架如 <a href="http://www.hibernate.org/">Hibernate</a>，可以減輕我們開發資料存取功能的實作負擔。</p>
<pre class="brush: java">
public UserDataAccessor {
    public void insert(User obj);
    public void update(User obj);
    public BizObject get(Serializable id);
    public void remove(User obj);
    User [] find(CriteriaExpression criteria,
        ParameterContext context,
        int maxCount);
}
</pre>
<pre class="brush: java">
public class UserDataAccessorImpl
    implements UserDataAccessor
    extends HibernateDataAccessor {

    public UserDataAccessorImpl() {
        super();
    }

    public void insert(User user) {
        insertBizObj(user);
    }

    public User get(Serializable id) {
        return (User) getBizObj(id);
    }

    public void update(User user) {
    	updateBizObj(user);
    }

    public void remove(User user) {
        removeBizObject(user);
    }

    public User [] find(
        CriteriaExpression criteria,
        ParameterContext context,
        int maxCount) {
        List userList = findBizObjs(criteria,
            context, maxCount);
        User users[] = new User[userList.size()];

        for (int i = 0; i < userList.size();
            i ++) {
            users[i] = userList.get(i);
        }

        return users;
    }
}
</pre>
<p>以上的程式是假設我們已經開發好供資料存取物件繼承的 <em>HibernateDataAccessor</em> 的基礎類別。它被定義為 abstract class，只提供 protected methods 供衍生類別呼叫物件存取的功能，例如上面程式片段的 <em>insertBizObj</em>、<em>updateBizObj</em>、<em>getBizObj</em>、<em>removeBizObj</em>、以及 <em>findBizObjs</em> 等方法。這樣使得永續存取物件的實作負擔並不重，但美中不足的是仍然因應不同的物件開發他們所需要的永續存取物件，而各個永續存取物件之間最大的差異卻只是不同物件的轉型而已，而顯得有些重覆而笨拙。</p>
<p>所幸 <a href="http://en.wikipedia.org/wiki/Java_(programming_language)">Java</a> 5.0 以後提供的<a href="http://en.wikipedia.org/wiki/Generic_programming">泛型程式設計</a>，可以幫我們參數化存取物件的型態，這樣就可讓資料存取的處理程序，與資料形態完全分離。因此我們可以設計適用存取各種資料型態的泛型資料存取界面如下。</p>
<pre class="brush: java">
public interface DataAccessor&lt;BizObject,
    LockType&gt;
    extends Persister&lt;BizObject,
    LockType&gt;,
    FinderExecutor&lt;BizObject&gt; {
}

public interface Persister&lt;BizObject,
    LockType&gt; {
    public void insert(BizObject obj);
    public void update(BizObject obj,
        LockType lock);
    public BizObject get(Serializable id,
        LockType lock);
    public void remove(BizObject obj,
        LockType lock);
}

public interface FinderExecutor&lt;BizObject&gt; {
    List&lt;BizObject&gt; find(
        CriteriaExpression criteria,
        ParameterContext context, int maxCount);
    void addFilter(String name, Map&lt;String,
        Object&gt; params);
    void removeFilter(String name);
}
</pre>
<p>為了讓資料存取的權責更清楚而明確，我們使 <em>DataAccessor</em> 組合兩種抽象概念，一個是永續物件的存取、另一個則是找出符合查詢條件的永續物件序列。依據 <a href="http://en.wikipedia.org/wiki/OOAD">OOAD</a> 的 <a href="http://www.objectmentor.com/resources/articles/isp.pdf">ISP</a> 原則，將這兩種不同概念分離成兩個界面，並由實作 <em>DataAccessor</em> 界面的程式來分別實作這兩個界面。</p>
<pre class="brush: java">
public class HibernateDataAccessor&lt;
    BizObject&gt; implements
    DataAccessor&lt;BizObject, LockMode&gt; {

    private Class&lt;BizObject&gt; bizObjType;
    private SessionFactory sessionFactory = null;
    private HibernateTemplate hibernateTemplate
        = null;

    public static int MAX_COUNT_IS_NOT_LIMITED
        = 0;

    public HibernateDataAccessor(
        Class&lt;BizObject&gt; bizObjType) {
        super();
        this.bizObjType = bizObjType;
    }

    public void insert(BizObject obj) {
        if (hibernateTemplate != null) {
            hibernateTemplate.save(obj);
            hibernateTemplate.flush();
        }
    }

    public BizObject get(Serializable id,
        LockMode lock) {
    	BizObject bizObj = null;

        if (id != null &#038;&#038;
            hibernateTemplate != null) {
            hibernateTemplate.flush();
            hibernateTemplate.get(bizObjType, id,
                lock);
        }

        return bizObj;
    }

    public void update(BizObject obj,
        LockMode lock) {
        if (hibernateTemplate != null) {
            hibernateTemplate.update(obj, lock);
            hibernateTemplate.flush();
        }
    }

    public void remove(BizObject obj,
        LockMode lock) {
        if (hibernateTemplate != null) {
            hibernateTemplate.delete(obj, lock);
            hibernateTemplate.flush();
        }
    }

    public void setSessionFactory(
        SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        this.hibernateTemplate =
            new HibernateTemplate(
                this.sessionFactory);
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public List&lt;BizObject&gt; find(
        CriteriaExpression criteria,
        ParameterContext context, int maxCount) {

        if (hibernateTemplate != null)	{
            QueryCriteria fromBizObject =
                QueryCriteria.from(bizObjType);
            HibernateHqlQueryBuilder builder =
                getHqlBuilder(
                fromBizObject.where(criteria,
                context));

            hibernateTemplate.flush();
            hibernateTemplate.setMaxResults(
                maxCount);

            if (builder.getParamValues().length
                > 0) {
                return hibernateTemplate.find(
                    builder.getHqlText(),
                    builder.getParamValues());
            } else {
                return hibernateTemplate.find(
                    builder.getHqlText());
            }
        }
        else {
            return null;
        }
    }

    private HibernateHqlQueryBuilder
        getHqlBuilder(
        QueryCriteria criteria) {
        HibernateHqlQueryBuilder builder =
            new HibernateHqlQueryBuilder();
        CriteriaQueryDirector director =
            new CriteriaQueryDirector(builder);

        director.makeQuery(criteria,
            criteria.getContext());

        return builder;
    }

    public void addFilter(String name, Map&lt;String,
        Object&gt; params) {
        if (hibernateTemplate != null)
        {
            Filter filter =
                hibernateTemplate.enableFilter(
                name);
            Iterator&lt;
                Map.Entry&lt;String,
                Object&gt; &gt; it
                    = params.entrySet().iterator();

            while(it.hasNext())
            {
                Map.Entry&lt;String, Object&gt; mapEntry
                    = it.next();
                filter.setParameter(
                    mapEntry.getKey(),
                    mapEntry.getValue());
            }
        }
    }

    public void removeFilter(String name) {
        if (hibernateTemplate != null) {
            final String filterName = name; 

            hibernateTemplate.execute(
                new HibernateCallback() {
                public Object doInHibernate(
                    Session session)
                    throws HibernateException,
                    SQLException {
                        session.disableFilter(
                            filterName);

                        return null;
                }
            });
        }
    }
}
</pre>
<p>上面這段程式用到同人過去分享的「<a href="http://www.lifeparty.idv.tw/blog/archives/192">永續物件查詢的設計</a>」來一般化查詢商業物件的方法，讓我們不會受到 ORM 的框架而影響影響 DataAccossor 的操作界面。同人建立一個共通的表述語法稱為 QueryExpression，讓查詢商業物件的實作方式不會因為永續框架的改變而受到影響。</p>
<p>再加上 BizObject 與 LockMode 型態的參數化，使我們不需要為不同的物件型態或永續框架的鎖定機制而重覆開發資料存取元件。此外，考慮物件可能會有需要事先定義過濾條件的需求，我們也在 <em>FinderExecutor</em> 界面中加入了 <em>AddFilter()</em> 及 <em>RemoveFilter()</em> 的操作界面。使用 Hibernate 的泛型資料存取元件，只須略為修改我們的會員服務的實作，我們根本無須額外開發會員資料的資料存取物件。</p>
<pre class="brush: java">
public class MemberServiceImpl
    implements MemberService {

    private DataAccessor&lt;User,
        LockMode&gt; memberDataAccessor
            = null;

    public List&lt;User&gt; getMembers(
        CriteriaExpression criteria,
        ParameterContext context,
        int maxCount) {
            return memberDataAccessor.find(
                criteria, context, maxCount);
	}

    public void registerMember(
        User member) {
        memberDataAccessor.insert(
            member);
    }

    public void updateMemberInfo(
        User member) {
        memberDataAccessor.update(
            member, LockMode.UPGRADE);
    }

    public void setMemberDataAccessor(
        DataAccessor&lt;User, LockMode&gt;
        memberDataAccessor) {
        this.memberDataAccessor
            = memberDataAccessor;
	}

    public DataAccessor&lt;User, LockMode&gt;
        getMemberDataAccessor() {
        return memberDataAccessor;
    }

    public void removeMember(User member) {
        memberDataAccessor.remove(
            member, LockMode.UPGRADE);
    }

}
</pre>
<p>我們只須在注入 memberDataAccessor 時，以 User 的資料型態傳入建構子的參數初始化資料存取物件，就不需要再為 User 開發資料存取物件了，因為資料存取的處理邏輯與資料型態已經分離，彼此不再相互耦合，這正是泛型技術帶給我們的一大優勢呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/449/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>訊息交易的抽象化思考</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/431</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/431#comments</comments>
		<pubDate>Tue, 03 Feb 2009 10:00:22 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[品質文化]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[易經思維]]></category>
		<category><![CDATA[生活感觸]]></category>
		<category><![CDATA[編程技巧]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/431</guid>
		<description><![CDATA[在〈開發者的 common sense〉的留言中，同人看到一些網友的批評。我發現這些批評顯示了有些開發者不擅於抽象化思考，而習慣於用經驗法則來取代思考。然而，誠如 Brooks 所言：「軟體的本質是複雜的，而不是偶然發生的」對治複雜度本來就是開發者的天職，而軟體開發的抽象化思考則是其用以統理複雜度的利器。由此看來，那些網友的批評著實令人為他們捏一把冷汗呀。
從路邊的垃圾桶與路人的留言，我們可以發現他們弄錯 common sense 的意思。他們認為 common sense 不能一概而論，因為每個人的 common sense 都不同。但這樣的觀點令人感到疑惑，如果每個人的 common sense 都不一樣，所以無法一概而論，那這種 common sense 還能叫做 common sense 嗎？
到底他們觀點上邏輯的矛盾，問題是出在那裡呢？同人認為問題並不是開發者的 common sense 不存在，而是忽略了開發者應該將經驗化成一般性的通用概念。舉例來說，軟體工程領域本身就是從實務發展出來的理論，其中許多概念就是開發者必須知道的 common sense，對開發者來說是合理的知識，也是他們都知道、無須解釋或加以論證的常識。
由此可知，如果開發者缺乏一般性的通用概念，那他碰到問題就很難舉一反三，自然也就難以掌握重點，而只能依據表相來處理問題，往往使得問題變得更複雜。oofunp 的留言就很像欠缺概念思考的開發者常見的反應，一開始以自己熟悉的技術來看問題，最後才發現自己對問題的理解是錯誤的。尤其「以為抽象化是將資料庫定義抽象化」的想法，更是整個弄錯抽象化思考的意義，結果最後他還是誤解了業務規則的意思。
同人前一篇文章所提到的業務規則，並非來自技術領域上萬用的設計，而是對問題領域經過抽象化思考後，所萃取而得到可以解決業務需求的重要概念。顯然 oofunp 的誤解是以技術的角度來看待抽象化思考，才會產生嚴重的觀念混淆。事實上，抽象化思考重視的不是技術實作，而是如何從實際問題當中萃取出重要的抽象概念，以增進我們對問題領域的瞭解，才能採用最適當的技術來開發軟體系統。
此外，過份強調技術經驗而輕忽概念性思維的開發者，很容易表現出自己對問題的盲目。就像 X files 留言的批評一樣，責怪同人沒有交待清楚是 XML 格式的問題，直到別人提出質疑才說明與列出參考文獻，認為同人缺乏部落格文章寫作的 common sense。但我的文章已經很清楚地提到是有關「交易訊息」語法的問題，難道他不瞭解交易訊息是 XML 技術的一般化抽象概念表述嗎？XML 只是實現交易訊息概念的一種技術，用以解決跨系統整合的問題。如果他要看到 XML 字眼才知道是訊息格式的問題，那改天換了另一種交易訊息的實作方式，我想大概他腦筋又要轉不過來了吧。
以上網友的三種批評，表面上看起來好像是不同的問題，但其背後都存在同樣的本質，那就是從交易訊息的問題中可見一斑，他們無法以抽象性思考來看待軟體開發的問題。但為什麼開發者需要抽象化思考呢？可能有人會認為，只有 OOA、OOD、或是 OOP 才會需要抽象化思考，不用物件導向的開發典範，應該不需要抽象化思考。其實這樣的觀念是錯誤的，不管我們採用什那一種開發典範來開發系統，抽象化思考都是必備的能力，只是不同的開發典範需要不同的抽象層面。
 抽象思考本來就是人類本能的思維能力，讓我們可以因應變化掌握事物的核心關鍵。這正是《易經繫辭》所言「一陰一陽之謂道，繼之者善也，成之者性也。仁者見之謂之仁，知者見之謂之知，百姓日用而不知。故君子之道鮮矣」的道理，我們常在不知不覺中運用抽象化的概念。最近看到《世紀末軟體革命復刻版》說得好，從小我們就具備了抽象的思考能力，我們可以把我們所關心的特性，從事物中「抽」出來，看出一些形而上、感官之外、富於意義的特性來。
這本書還提到有關抽象化思考的一個重點，事物的意義是針對問題的需要而來。世間事物的特性多到數不清，但藉由抽象化的過程，我們可以把它抽象化為只剩下一些我們需要的特性。我們只要掌握這些關心的特性，就可以讓世間的事物更容易處理。例如一般人所知道的「冰」，在愛斯基摩人眼中卻可分類為十幾種，只因為需要不一樣；我們只要知道冰的概念就行了，但愛斯基摩人對於冰的用處有許多，所以需要更細膩冰的分類。因此，因應需要的不一樣，抽象化思考的結果也會有所不同。
讓我們回到訊息交易系統的開發，抽象化思考通常被稱做資訊隱藏，萃取最有意義的資訊，並去除其它不必要的資訊。以欄位內容取值為例，空白或是空值都只是眾多可能參考欄位內含值的一種，不論空白與否，都不影響系統實際運作的處理程序，因此空白或空值與否的資訊，對系統並不重要應予以略除，這是屬於語法層次的抽象化思考。
當然在實際業務的需要上，空白或空值可能會有特殊的意義，但那並非欄位取值過程本身應該處理的問題，而是由取值後續的檢核資料合理性的規則來處理，這是屬於語意層次的抽象性思考。雖然為了系統執行效能的考量，語法與語意可能會寫在同一支程式或程序當中。但以程式的設計概念來看，因為技術實作的因素混淆語意與語法，而破壞設計概念的整體性，則是無異於自廢武功而使軟體品質問題層出不窮。
相信沒有人會否認良好的設計概念應該是「分而治之」，我們在某些訊息交易的標準中，也確實看得到語法與語意分而治之的例子。例如一些 EDI 的訊息標準會以 MIG（訊息建置指引）來規範交易訊息格式，包括欄位的強制性、可重覆出現的次數、欄位可能的內含值來代表訊息的語法，而且多半是通行於相同功能領域的共通語法。而對於相同功能領域不同的交易，則會以 SIG（系統建置指引）來規範特定交易的訊息語意，用來關聯到實務上的業務規則。
早年的 EDI 訊息交易的系統，很多都不是用物件導向的方法所實作出來的，但其系統結構卻與物件導向的抽象化思維不謀而合，這代表什麼呢？同人還是喜歡引用《易經繫辭》的道理，正是「天下事一致而百慮，殊途而同歸」、「易窮則變、變則通、通則久」，事物的核心本質本來就存在，只在於我們如何找出有意義的概念出來，並且加以演化而讓系統不斷進展，重點還在於我們懂不懂得抽象化思考呀。
]]></description>
			<content:encoded><![CDATA[<p>在〈<a href="http://www.lifeparty.idv.tw/blog/archives/430">開發者的 common sense</a>〉的留言中，同人看到一些網友的批評。我發現這些批評顯示了有些開發者不擅於<a href="http://en.wikipedia.org/wiki/Abstraction">抽象化思考</a>，而習慣於用經驗法則來取代思考。然而，誠如 <a href="http://en.wikiquote.org/wiki/Fred_Brooks">Brooks</a> 所言：「軟體的本質是複雜的，而不是偶然發生的」對治複雜度本來就是開發者的天職，而軟體開發的抽象化思考則是其用以統理複雜度的利器。由此看來，那些網友的批評著實令人為他們捏一把冷汗呀。</p>
<p>從路邊的垃圾桶與路人的留言，我們可以發現他們弄錯 common sense 的意思。他們認為 common sense 不能一概而論，因為每個人的 common sense 都不同。但這樣的觀點令人感到疑惑，如果每個人的 common sense 都不一樣，所以無法一概而論，那這種 common sense 還能叫做 common sense 嗎？</p>
<p>到底他們觀點上邏輯的矛盾，問題是出在那裡呢？同人認為問題並不是開發者的 common sense 不存在，而是忽略了開發者應該將經驗化成一般性的通用概念。舉例來說，軟體工程領域本身就是從實務發展出來的理論，其中許多概念就是開發者必須知道的 common sense，對開發者來說是合理的知識，也是他們都知道、無須解釋或加以論證的<a href="http://zh.wikipedia.org/w/index.php?title=常識&amp;variant=zh-tw">常識</a>。</p>
<p>由此可知，如果開發者缺乏一般性的通用概念，那他碰到問題就很難舉一反三，自然也就難以掌握重點，而只能依據表相來處理問題，往往使得問題變得更複雜。oofunp 的留言就很像欠缺概念思考的開發者常見的反應，一開始以自己熟悉的技術來看問題，最後才發現自己對問題的理解是錯誤的。尤其「以為抽象化是將資料庫定義抽象化」的想法，更是整個弄錯抽象化思考的意義，結果最後他還是誤解了業務規則的意思。</p>
<p>同人前一篇文章所提到的業務規則，並非來自技術領域上萬用的設計，而是對問題領域經過抽象化思考後，所萃取而得到可以解決業務需求的重要概念。顯然 oofunp 的誤解是以技術的角度來看待抽象化思考，才會產生嚴重的觀念混淆。事實上，抽象化思考重視的不是技術實作，而是如何從實際問題當中萃取出重要的抽象概念，以增進我們對問題領域的瞭解，才能採用最適當的技術來開發軟體系統。</p>
<p>此外，過份強調技術經驗而輕忽概念性思維的開發者，很容易表現出自己對問題的盲目。就像 X files 留言的批評一樣，責怪同人沒有交待清楚是 XML 格式的問題，直到別人提出質疑才說明與列出參考文獻，認為同人缺乏部落格文章寫作的 common sense。但我的文章已經很清楚地提到是有關「交易訊息」語法的問題，難道他不瞭解交易訊息是 XML 技術的一般化抽象概念表述嗎？XML 只是實現交易訊息概念的一種技術，用以解決跨系統整合的問題。如果他要看到 XML 字眼才知道是訊息格式的問題，那改天換了另一種交易訊息的實作方式，我想大概他腦筋又要轉不過來了吧。</p>
<p>以上網友的三種批評，表面上看起來好像是不同的問題，但其背後都存在同樣的本質，那就是從交易訊息的問題中可見一斑，他們無法以抽象性思考來看待軟體開發的問題。但為什麼開發者需要抽象化思考呢？可能有人會認為，只有 <a href="http://en.wikipedia.org/wiki/Object-oriented_analysis_and_design">OOA、OOD</a>、或是 <a href="http://en.wikipedia.org/wiki/Object_Oriented">OOP</a> 才會需要抽象化思考，不用物件導向的開發典範，應該不需要抽象化思考。其實這樣的觀念是錯誤的，不管我們採用什那一種開發典範來開發系統，抽象化思考都是必備的能力，只是不同的開發典範需要不同的抽象層面。</p>
<p><a href="http://www.anobii.com/books/002b0bcc4b2bca5604/" title="更多關於世紀末軟體革命復刻版"><img src="http://image.anobii.com/anobi/image_book.php?type=4&amp;item_id=002b0bcc4b2bca5604&amp;time=0" style="padding: 5px; display: inline; float: right" title="更多關於世紀末軟體革命復刻版" alt="世紀末軟體革命復刻版的圖像" width="93" height="132" /></a> 抽象思考本來就是人類本能的思維能力，讓我們可以因應變化掌握事物的核心關鍵。這正是《易經繫辭》所言「一陰一陽之謂道，繼之者善也，成之者性也。仁者見之謂之仁，知者見之謂之知，百姓日用而不知。故君子之道鮮矣」的道理，我們常在不知不覺中運用抽象化的概念。最近看到《<a href="http://www.anobii.com/books/002b0bcc4b2bca5604/" title="更多關於世紀末軟體革命復刻版">世紀末軟體革命復刻版</a>》說得好，從小我們就具備了抽象的思考能力，我們可以把我們所關心的特性，從事物中「抽」出來，看出一些形而上、感官之外、富於意義的特性來。</p>
<p>這本書還提到有關抽象化思考的一個重點，事物的意義是針對問題的需要而來。世間事物的特性多到數不清，但藉由抽象化的過程，我們可以把它抽象化為只剩下一些我們需要的特性。我們只要掌握這些關心的特性，就可以讓世間的事物更容易處理。例如一般人所知道的「冰」，在愛斯基摩人眼中卻可分類為十幾種，只因為需要不一樣；我們只要知道冰的概念就行了，但愛斯基摩人對於冰的用處有許多，所以需要更細膩冰的分類。因此，因應需要的不一樣，抽象化思考的結果也會有所不同。</p>
<p>讓我們回到訊息交易系統的開發，抽象化思考通常被稱做<a href="http://en.wikipedia.org/wiki/Information_hiding">資訊隱藏</a>，萃取最有意義的資訊，並去除其它不必要的資訊。以欄位內容取值為例，空白或是空值都只是眾多可能參考欄位內含值的一種，不論空白與否，都不影響系統實際運作的處理程序，因此空白或空值與否的資訊，對系統並不重要應予以略除，這是屬於語法層次的抽象化思考。</p>
<p>當然在實際業務的需要上，空白或空值可能會有特殊的意義，但那並非欄位取值過程本身應該處理的問題，而是由取值後續的檢核資料合理性的規則來處理，這是屬於語意層次的抽象性思考。雖然為了系統執行效能的考量，語法與語意可能會寫在同一支程式或程序當中。但以程式的設計概念來看，因為技術實作的因素混淆語意與語法，而破壞設計概念的整體性，則是<a href="http://www.lifeparty.idv.tw/blog/archives/262">無異於自廢武功而使軟體品質問題層出不窮</a>。</p>
<p>相信沒有人會否認良好的設計概念應該是「分而治之」，我們在某些訊息交易的標準中，也確實看得到語法與語意分而治之的例子。例如一些 <a href="http://en.wikipedia.org/wiki/Electronic_Data_Interchange">EDI</a> 的訊息標準會以 MIG（訊息建置指引）來規範交易訊息格式，包括欄位的強制性、可重覆出現的次數、欄位可能的內含值來代表訊息的語法，而且多半是通行於相同功能領域的共通語法。而對於相同功能領域不同的交易，則會以 SIG（系統建置指引）來規範特定交易的訊息語意，用來關聯到實務上的業務規則。</p>
<p>早年的 EDI 訊息交易的系統，很多都不是用物件導向的方法所實作出來的，但其系統結構卻與物件導向的抽象化思維不謀而合，這代表什麼呢？同人還是喜歡引用《易經繫辭》的道理，正是「天下事一致而百慮，殊途而同歸」、「易窮則變、變則通、通則久」，事物的核心本質本來就存在，只在於我們如何找出有意義的概念出來，並且加以演化而讓系統不斷進展，重點還在於我們懂不懂得抽象化思考呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/431/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>開發者的 common sense</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/430</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/430#comments</comments>
		<pubDate>Fri, 23 Jan 2009 07:22:54 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[專案團隊]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[溝通]]></category>
		<category><![CDATA[生活感觸]]></category>
		<category><![CDATA[職場]]></category>
		<category><![CDATA[衝突]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/430</guid>
		<description><![CDATA[最近某位開發者和同人討論需求規格的問題，但他的反應卻讓人感到困惑，不知是他的理解能力有問題，還是面對問題太過情緒化？以下是我們對話的內容。
開發者 D 君問同人：「規格好像沒有提到欄位空白該如何處理？」
同人回答：「沒特別說明就是代表將該欄位填入空白。」
D 君說：「為什麼不是未指定欄位內容呢？」
同人說：「如果是那樣，該欄位不應該在交易訊息中出現；但如果該欄位的內容是空白，那就應該不指定訊息欄位的值。」
D 君說：「不過，從交易訊息的定義來看，那個欄位是必要欄位，不可能不出現。」
同人說：「所以那個欄位是必要的，訊息中沒有指定值就代表欄位要填入空白。」
D 君說：「那規格應該交待這個細節？」
同人說：「不需要，規格文件不寫語法而只會記載語意，因為語法是屬於 common sense，沒必要詳盡記錄在規格文件中。不然，如果連 common sense 都要寫在文件上的話，那是否意味程式設計者也不需要懂程式語言了，反正文件上都會寫。」
D 君說：「我知道了，你的意思是說我沒有 common sense！」
同人說：「如果你覺得我那裡說你沒 common sense，請明說我可以向你道歉，否則你這種情緒化的言論，只會讓人感到不舒服！」
D 君：&#8230;
同人將這件事寫在噗浪上，有噗友認為這類的開發者能力不行，沒什麼產值卻會製造問題。不過，在此事我所看到的問題倒不是開發者能力，而是認為重點在開發者只看文件做事的心態。開發者傾向用詳盡的文件來取代個人的思考與互動的溝通，這才是我認為最可怕的事情。
以同人的觀察來看，許多開發者看規格文件，通常不會站在問題的角度來思考，而是要求別人把答案準備好，然後告訴他們照著把功能做出來。
他們認為文件必須要註明他需要用到的所有資訊，而不要期待他們了解問題，因為他們不想花太多的心力來弄懂它，如果程式照規格開發而出現問題，那必然是規格問題而不是他們的疏失。因此，他們根本不在乎規格記錄語法或是語意，反正有寫就照著做，沒有寫的就不用管它，也不用思考合不合乎他們的 common sense。
同人認為這種心態正是忽略軟體開發的複雜本質，特別是軟體開發常會碰到半結構化、甚至是非結構的問題，使得規格文件難以事先將所有可能遇到的狀況都定義清楚。
舉個常見的例子來說，系統發生某些特別的異常狀態多半不是事前可以預料到的，沒有人能在事前發現問題，但等到發生意外才會發現問題在那裡。而且當我們發現到愈多問題，我們就會發現更多難以預料的未知的問題。
因此，開發者期待文件詳盡到無所不包，在專案現實是不可能做到的。實務上，通常文件規格的重點在於瞭解問題或如何解決問題，而並非得到詳盡的規格。但不明究理的開發者總是不去瞭解問題，只關心規格實做上的細節，當然很難掌握解決問題的要領。同人想到溫伯格說的「沒問題病候群」，他建議碰到這種人還是閃遠一點吧。
其實，表面上 D 君碰到的問題，好像是更改對語法的定義就可以解決問題；客戶認為交易訊息中的欄位不應該是空白的，所以訊息中的欄位空白應該代表該欄位內容未指定。但這樣的想法將會使得語法會受到業務規則的衝擊，而破壞設計概念的整體性與一致性，而影響軟體的設計結構。事實上，語法是不應該受到業務規則的影響，而是用語意來解決業務邏輯的合理性問題。
如果該欄位是必要欄位，那訊息中此欄位出現空白就應該認定資料錯誤；但如果它並不是必要欄位，那麼就應該定義訊息此欄位為非必要欄位。而在前者的狀況下，我們應該增加一條業務規則來檢核必要欄位不可為空白，否則將拋出異常的回應。如此就非常簡單解決問題，而不該採取自廢武功的作為，因為那只會顯露出開發者缺乏抽象化思考的 common sense 呀。
]]></description>
			<content:encoded><![CDATA[<p>最近某位開發者和同人討論需求規格的問題，但他的反應卻讓人感到困惑，不知是他的理解能力有問題，還是面對問題太過情緒化？以下是我們對話的內容。</p>
<blockquote><p>開發者 D 君問同人：「規格好像沒有提到欄位空白該如何處理？」</p>
<p>同人回答：「沒特別說明就是代表將該欄位填入空白。」</p>
<p>D 君說：「為什麼不是未指定欄位內容呢？」</p>
<p>同人說：「如果是那樣，該欄位不應該在交易訊息中出現；但如果該欄位的內容是空白，那就應該不指定訊息欄位的值。」</p>
<p>D 君說：「不過，從交易訊息的定義來看，那個欄位是必要欄位，不可能不出現。」</p>
<p>同人說：「所以那個欄位是必要的，訊息中沒有指定值就代表欄位要填入空白。」</p>
<p>D 君說：「那規格應該交待這個細節？」</p>
<p>同人說：「不需要，規格文件不寫語法而只會記載語意，因為語法是屬於 common sense，沒必要詳盡記錄在規格文件中。不然，如果連 common sense 都要寫在文件上的話，那是否意味程式設計者也不需要懂程式語言了，反正文件上都會寫。」</p>
<p>D 君說：「我知道了，你的意思是說我沒有 common sense！」</p>
<p>同人說：「如果你覺得我那裡說你沒 common sense，請明說我可以向你道歉，否則你這種情緒化的言論，只會讓人感到不舒服！」</p>
<p>D 君：&#8230;</p></blockquote>
<p>同人將這件事<a href="http://www.plurk.com/p/dph1f">寫在噗浪</a>上，有噗友認為這類的開發者能力不行，沒什麼產值卻會製造問題。不過，在此事我所看到的問題倒不是開發者能力，而是認為重點在開發者只看文件做事的心態。開發者傾向用詳盡的文件來取代個人的思考與互動的溝通，這才是我認為最可怕的事情。</p>
<p>以同人的觀察來看，許多開發者看規格文件，通常不會站在問題的角度來思考，而是要求別人把答案準備好，然後告訴他們照著把功能做出來。</p>
<p>他們認為文件必須要註明他需要用到的所有資訊，而不要期待他們了解問題，因為他們不想花太多的心力來弄懂它，如果程式照規格開發而出現問題，那必然是規格問題而不是他們的疏失。因此，他們根本不在乎規格記錄語法或是語意，反正有寫就照著做，沒有寫的就不用管它，也不用思考合不合乎他們的 <a href="http://en.wikipedia.org/wiki/Common_sense">common sense</a>。</p>
<p>同人認為這種心態正是忽略軟體開發的複雜本質，特別是軟體開發常會碰到半結構化、甚至是非結構的問題，使得規格文件難以事先將所有可能遇到的狀況都定義清楚。</p>
<p>舉個常見的例子來說，系統發生某些特別的異常狀態多半不是事前可以預料到的，沒有人能在事前發現問題，但等到發生意外才會發現問題在那裡。而且當我們發現到愈多問題，我們就會發現更多難以預料的未知的問題。</p>
<p>因此，開發者期待文件詳盡到無所不包，在專案現實是不可能做到的。實務上，通常文件規格的重點在於瞭解問題或如何解決問題，而並非得到詳盡的規格。但不明究理的開發者總是不去瞭解問題，只關心規格實做上的細節，當然很難掌握解決問題的要領。同人想到溫伯格說的「<a href="http://www.geraldmweinberg.com/Bookstuff/Each_Book/BTL.html">沒問題病候群</a>」，他建議碰到這種人還是閃遠一點吧。</p>
<p>其實，表面上 D 君碰到的問題，好像是更改對語法的定義就可以解決問題；客戶認為交易訊息中的欄位不應該是空白的，所以訊息中的欄位空白應該代表該欄位內容未指定。但這樣的想法將會使得語法會受到業務規則的衝擊，而破壞設計概念的整體性與一致性，而影響軟體的設計結構。事實上，語法是不應該受到業務規則的影響，而是用語意來解決業務邏輯的合理性問題。</p>
<p>如果該欄位是必要欄位，那訊息中此欄位出現空白就應該認定資料錯誤；但如果它並不是必要欄位，那麼就應該定義訊息此欄位為非必要欄位。而在前者的狀況下，我們應該增加一條業務規則來檢核必要欄位不可為空白，否則將拋出異常的回應。如此就非常簡單解決問題，而<a href="http://www.lifeparty.idv.tw/blog/archives/262">不該採取自廢武功的作為</a>，因為那只會顯露出開發者缺乏<a href="http://en.wikipedia.org/wiki/Abstraction">抽象化</a>思考的 common sense 呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/430/feed</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>再談程式設計的迷思</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/424</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/424#comments</comments>
		<pubDate>Fri, 16 Jan 2009 08:19:14 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[CNet/ZDNet]]></category>
		<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[學習]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[編程技巧]]></category>
		<category><![CDATA[職場]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/424</guid>
		<description><![CDATA[昨天同人在〈又見少了概括性論點〉提到〈必須面對的真相─五大程式設計迷思〉在文章結構上的問題。其實那篇文章除了結構問題之外，同人也在該篇文章內容中，發現了一些值得探討的問題，因此想在這篇文章發表我的看法。
以該篇文章內容而言，同人認為值得探討的有兩個地方，一個是程式語言一直再改變的迷思、另一個則是作者建議讀者，儘量避免用遞迴的方式來寫作程式。第一個問題只是沒有交待清楚作者想要表達的概念，而第二個問題就是嚴重的偏見了，值得讓人進行思辨以建立正確的觀念。
為什麼程式語言一直再改變是個迷思？那篇文章就有讀者質疑這個迷思，他說：
關於這部份，我覺得不如講＂只有XXX語言才能作某件事情＂
我不覺得＂程式語言的改變＂是迷思
不過，同人認為所謂「程式語言一直在改變」的迷思，作者的意思不單單只是說程式語言沒有一直在改變的意思，而是還有更深層的涵義沒有表達出來。事實上，程式語言一直再改變是個事實，我們不該說它們沒有一直改變。而是這些改變並不會讓我們先前對程式語言學習的投資白費。
同人認為，作者提到「電腦程式語言的演進，就像人類的語言演進一樣，有其一定的過程」是正確的。但對程式語言的學習而言，重點並不在於「程式語言不會一直在改變」，而是在於改變通常不會影響程式語言舊版本的語法，就算存在一些新舊版本不相容的影響，但只要掌握基本觀念，新的改變是不需要重新學起，而且我們經常還能在不同的程式語言之間，發現彼此相通的地方。
其實程式語言會不斷演化是必然的，「成熟」的程式語言反而是象徵衰退的開始。因為隨著時代的進步，程式設計的問題只會愈變愈複雜，造就了新的程式設計概念。如果程式語言太成熟，以致無法改變來加入新的程式設計概念，那麼它們將被時代所淘汰。所以，問題不在於程式語言是不是臻於成熟，因為改變是必然的，重點在於你投資在程式語言的學習並不會白費。
再來，作者提到「千萬不要認為電腦已經跑得很快了，就不注意演算方法的選擇。」同人認為是正確的。但他舉例指出遞迴函式效率較不佳、比非遞迴寫法的程式容易做重複計算，建議大家儘量避免用非遞迴的寫法來進行程式寫作的說法，同人就完全不能夠認同作者的看法。
沒有錯，遞迴函式的確比非遞迴的寫法會佔去更多的記憶體，但以空間換取時間的角度來看，說遞迴函式比非遞迴寫法更沒有效率，則是過份簡化的說法。即使以作者舉計算費式數列的遞迴函式的例子來看，遞迴函數可能有重複計算的問題，但到底費式數列的 n 值要多大，遞迴函數的執行速度才會發生顯著的影響，這卻是值得探討的問題。
如果在一般解決領域問題的條件限制上，都不見某種演算法的缺點，會產生顯著的影響，但卻能享受到該種演算法的優點，那麼我們是不是應該選用這樣的演算法來設計程式呢？答案想必是肯定的。這不是把問題推到硬體速度夠不夠快的問題，而是演算法的選擇應該透過客觀的分析與理性的思考，才能找到最適當的選擇。
這讓人也發現到程式設計常見的迷思，很容易誤導我們對程式開發的看法；太過相信經驗法則而缺乏對問題的深入思考，容易人云亦云地以為該用什麼演算法或避免什麼演算法，而那些都是程式設計師自以為是的偏見。
我想到哈米尼斯對同人分享永續物件設計所提出的意見，他提到：
就現實問題而言，一家公司裡，可能許多套系統都是由不同的廠商開發，若以這樣的角度來看，這一個架構似乎有點問題。
再來則是效能考量的問題，我認同這一個架構下，可以讓開發和維護變得簡潔，但若是遇到某些特定的系統，如下單系統，它強調的重點會在反應時間及大量資料處理，若是以此為考量，這樣的架構似乎又繞了太多層？
哈米尼斯的意見正是同人在工作上常面臨的質疑，但實際上我所提出來的設計是經過驗證過的架構，運用服務導向的觀念、以及物件導向設計的設計手法，已成功整合銀行各種不同廠商開發的子系統。它實際應用在銀行的付款系統，所以對績效的要求是非常嚴苛的，然而實際上它的效能卻未見如哈米尼斯所顧慮到的問題。
受限於自己的經驗，很多程式設計師傾向於認定太多「系統間接層」不好，但那只是一己想法，而未經證實的猜測。如果真的擔心發生這樣的問題，應該採取行動進行驗證，而不是停留在想法的猜測，這才是程式設計者應有的積極態度。
事實上，間接層的設計只是把原來放在一起的程式碼區分好責任，去掉重覆性而已，我們對系統繞太多層的理解不一定是系統運作的真相。除非我們做了設計概念驗證，讓事實或數據說話，而不是經驗的推論，後者往往來自於個人徧見所致。
同人無意在這裡討論那一種程式開發典範比較好，因為那是不會有結果的爭論，只有在了解程式想要解決的問題之後，討論用什麼方式來開發程式才會有意義。
演算法的選擇也是同樣的道理，每一種演算法都有它的優缺點，我們應當針對問題的目標與限制來「思考」最適當的演算法，而非慣性地「記憶」那一種方法用在每一種情況都比較好、或比較不好。因此作者建議儘量避免用非遞迴的寫法，進行程式的開發，同人對此實在是感到不能認同。
更何況透過思考，遞迴函式重覆計算的問題並不難解決。例如我們可以在遞迴函數中，將已經計算過的數列項次值儲存下來，當過程中發現某個項次已經計算過了，我們可直接取值而避免再計算一次，這樣就可以增進遞迴函式的執行效能。而運用程式設計者的智慧，遞迴耗費大量記憶空間的限制，也並非不能克服。
同人一位朋友就分享他在繪圖程式設計的經驗，他曾改進過填色的遞迴函式。原來一個點向四面擴散填滿顏色要用到大量的遞迴空間，但只要改變以線或區域為單位向四面擴散，就能大幅減少填色所需之遞迴空間，克服遞迴函式的先天限制。
所以問題並不是遞迴可不可以用，而是程式設計者懂不懂得用思考來創新設計技巧；不懂得在解決問題上尋求挑戰與突破，那才是讓程式設計能力停滯不前的最可怕迷思呀。
]]></description>
			<content:encoded><![CDATA[<p>昨天同人在〈<a href="http://www.lifeparty.idv.tw/blog/archives/424">又見少了概括性論點</a>〉提到〈<a href="http://www.zdnet.com.tw/enterprise/column/jasonlee/0,2000090349,20134985,00.htm">必須面對的真相─五大程式設計迷思</a>〉在文章結構上的問題。其實那篇文章除了結構問題之外，同人也在該篇文章內容中，發現了一些值得探討的問題，因此想在這篇文章發表我的看法。</p>
<p>以該篇文章內容而言，同人認為值得探討的有兩個地方，一個是程式語言一直再改變的迷思、另一個則是作者建議讀者，儘量避免用遞迴的方式來寫作程式。第一個問題只是沒有交待清楚作者想要表達的概念，而第二個問題就是嚴重的偏見了，值得讓人進行思辨以建立正確的觀念。</p>
<p>為什麼程式語言一直再改變是個迷思？那篇文章就有讀者質疑這個迷思，他說：</p>
<blockquote><p>關於這部份，我覺得不如講＂只有XXX語言才能作某件事情＂<br />
我不覺得＂程式語言的改變＂是迷思</p></blockquote>
<p>不過，同人認為所謂「程式語言一直在改變」的迷思，作者的意思不單單只是說程式語言沒有一直在改變的意思，而是還有更深層的涵義沒有表達出來。事實上，程式語言一直再改變是個事實，我們不該說它們沒有一直改變。而是這些改變並不會讓我們先前對程式語言學習的投資白費。</p>
<p>同人認為，作者提到「電腦程式語言的演進，就像人類的語言演進一樣，有其一定的過程」是正確的。但對程式語言的學習而言，重點並不在於「程式語言不會一直在改變」，而是在於改變通常不會影響程式語言舊版本的語法，就算存在一些新舊版本不相容的影響，但只要掌握基本觀念，新的改變是不需要重新學起，而且我們經常還能在不同的程式語言之間，發現彼此相通的地方。</p>
<p>其實程式語言會不斷演化是必然的，「成熟」的程式語言反而是象徵衰退的開始。因為隨著時代的進步，程式設計的問題只會愈變愈複雜，造就了新的程式設計概念。如果程式語言太成熟，以致無法改變來加入新的程式設計概念，那麼它們將被時代所淘汰。所以，問題不在於程式語言是不是臻於成熟，因為改變是必然的，重點在於你投資在程式語言的學習並不會白費。</p>
<p>再來，作者提到「千萬不要認為電腦已經跑得很快了，就不注意演算方法的選擇。」同人認為是正確的。但他舉例指出遞迴函式效率較不佳、比非遞迴寫法的程式容易做重複計算，建議大家儘量避免用非遞迴的寫法來進行程式寫作的說法，同人就完全不能夠認同作者的看法。</p>
<p>沒有錯，遞迴函式的確比非遞迴的寫法會佔去更多的記憶體，但以空間換取時間的角度來看，說遞迴函式比非遞迴寫法更沒有效率，則是過份簡化的說法。即使以作者舉計算費式數列的遞迴函式的例子來看，遞迴函數可能有重複計算的問題，但到底費式數列的 n 值要多大，遞迴函數的執行速度才會發生顯著的影響，這卻是值得探討的問題。</p>
<p>如果在一般解決領域問題的條件限制上，都不見某種演算法的缺點，會產生顯著的影響，但卻能享受到該種演算法的優點，那麼我們是不是應該選用這樣的演算法來設計程式呢？答案想必是肯定的。這不是把問題推到硬體速度夠不夠快的問題，而是演算法的選擇應該透過客觀的分析與理性的思考，才能找到最適當的選擇。</p>
<p>這讓人也發現到程式設計常見的迷思，很容易誤導我們對程式開發的看法；太過相信經驗法則而缺乏對問題的深入思考，容易人云亦云地以為該用什麼演算法或避免什麼演算法，而那些都是程式設計師自以為是的偏見。</p>
<p>我想到哈米尼斯對同人分享<a href="http://www.lifeparty.idv.tw/blog/archives/192">永續物件設計</a>所提出的意見，他提到：</p>
<blockquote><p>就現實問題而言，一家公司裡，可能許多套系統都是由不同的廠商開發，若以這樣的角度來看，這一個架構似乎有點問題。</p>
<p>再來則是效能考量的問題，我認同這一個架構下，可以讓開發和維護變得簡潔，但若是遇到某些特定的系統，如下單系統，它強調的重點會在反應時間及大量資料處理，若是以此為考量，這樣的架構似乎又繞了太多層？</p></blockquote>
<p>哈米尼斯的意見正是同人在工作上常面臨的質疑，但實際上我所提出來的設計是經過驗證過的架構，運用服務導向的觀念、以及物件導向設計的設計手法，已成功整合銀行各種不同廠商開發的子系統。它實際應用在銀行的付款系統，所以對績效的要求是非常嚴苛的，然而實際上它的效能卻未見如哈米尼斯所顧慮到的問題。</p>
<p>受限於自己的經驗，很多程式設計師傾向於認定太多「系統間接層」不好，但那只是一己想法，而未經證實的猜測。如果真的擔心發生這樣的問題，應該採取行動進行驗證，而不是停留在想法的猜測，這才是程式設計者應有的積極態度。</p>
<p>事實上，間接層的設計只是把原來放在一起的程式碼區分好責任，去掉重覆性而已，我們對系統繞太多層的理解不一定是系統運作的真相。除非我們做了設計概念驗證，讓事實或數據說話，而不是經驗的推論，後者往往來自於個人徧見所致。</p>
<p>同人無意在這裡討論那一種程式開發典範比較好，因為那是不會有結果的爭論，只有在了解程式想要解決的問題之後，討論用什麼方式來開發程式才會有意義。</p>
<p>演算法的選擇也是同樣的道理，每一種演算法都有它的優缺點，我們應當針對問題的目標與限制來「思考」最適當的演算法，而非慣性地「記憶」那一種方法用在每一種情況都比較好、或比較不好。因此作者建議儘量避免用非遞迴的寫法，進行程式的開發，同人對此實在是感到不能認同。</p>
<p>更何況透過思考，遞迴函式重覆計算的問題並不難解決。例如我們可以在遞迴函數中，將已經計算過的數列項次值儲存下來，當過程中發現某個項次已經計算過了，我們可直接取值而避免再計算一次，這樣就可以增進遞迴函式的執行效能。而運用程式設計者的智慧，遞迴耗費大量記憶空間的限制，也並非不能克服。</p>
<p>同人一位朋友就分享他在繪圖程式設計的經驗，他曾改進過填色的遞迴函式。原來一個點向四面擴散填滿顏色要用到大量的遞迴空間，但只要改變以線或區域為單位向四面擴散，就能大幅減少填色所需之遞迴空間，克服遞迴函式的先天限制。</p>
<p>所以問題並不是遞迴可不可以用，而是程式設計者懂不懂得用思考來創新設計技巧；不懂得在解決問題上尋求挑戰與突破，那才是讓程式設計能力停滯不前的最可怕迷思呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/424/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>彈性分類群組策略的設計</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/389</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/389#comments</comments>
		<pubDate>Tue, 14 Oct 2008 07:35:37 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[編程技巧]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/389</guid>
		<description><![CDATA[最近寫了很多的占星文章，不知道有沒有人不習慣呀？為了擔心有些讀者無法適應，同人在此寫了一篇有關軟體開發的文章以中和主題，並用來分享我對彈性分類策略的設計心得。
許多的資料查詢，常會根據某些不同的分群觀點來將資料作分類。例如交易資料中的「產品分類」或是「單據種類」，使用者經常會希望將這些欄位當作查詢條件，讓他們可以找出相同產品分類或是單據分類的資料。
然而，實際上的查詢作業需求可能會比較複雜，也就是使用者可能會需要在一次資料查詢的結果，希望把不同資料分類，但彼此的分類之間卻同屬於相同分類群組的資料顯示在一起。我們可以新增資料分類群組的欄位，並依據資料分類來設定其分類群組的內容。這種作法在資料查詢方面的實作上非常簡便，但缺點則是要在資料的維護上額外花費一些成本。
因為這樣的設計會造成資料欄位的遞移相依，違反資料正規化的原則。當資料分類內容修改時，分類群組的內容也要跟著修正；而實際上很可能會因為程式的瑕疵或系統出現異常現象而造成資料不完整或缺乏一致性的風險。
而且，很多依據分類群組集中查詢的需求往往是在專案後期才提出來，如果開發者並不願意因此改動資料庫結構，要如何在不增加群組欄位的情況下，實作出依據分類群組集中查詢的功能呢？
答案當然是將分類群組欄位化成對應的查詢語法或群組欄位的顯示內容，這樣會讓查詢資料的實作程式變得複雜些。因為程式實作者除了必須知道資料的來源之外，尚須瞭解群組欄位與資料分類之間如何轉換。而當相同的查詢需求散布在系統各個程式之間時，就會增加程式開發的複雜度與出錯機率。尤其使用者可能會視作業方式來增加分類群組的需求，開發者應該如何運用設計的彈性，以降低程式開發沒有必要的複雜度呢？
同人在此提出一種設計手法，運用彈性分類群組策略的設計來解決以上的問題，請參考如下所示的設計模型。

此設計模型定義了分類群組類別 TypeGroup，運用此類別可產生新的分類群組，指定群組名稱、說明、以及其分群依據的資料分類欄位。而為了將分類群組的查詢及資料顯示演算法封裝起來，我們運用 Stratrgy Pattern 封裝了 getFindCriteria()、及 getGroupValue() 等操作界面成為 GroupStrategy 界面，而將查詢及顯示分類群組的工作交由實作此界面的類別。
於是，我們便可抽離查詢及顯分類群組的操作，將它們委託給隱藏於內的策略物件。而此策略物件則可視實際的需要加以抽換，而主要的資料查詢程式則不會受到群組演算法的更動而受到影響。
當然策略物件我們可運用 Abstract Factory 產生，但更簡便的作法則是可交由 spring framework 來管理其策略物件的生命期。如此，彈性分類群組策略的設計更能夠享受到元件化開發的好處了。
]]></description>
			<content:encoded><![CDATA[<p>最近寫了很多的<a href="http://www.lifeparty.idv.tw/blog/archives/category/astrolog">占星文章</a>，不知道有沒有人不習慣呀？為了擔心有些讀者無法適應，同人在此寫了一篇有關軟體開發的文章以中和主題，並用來分享我對彈性分類策略的設計心得。</p>
<p>許多的資料查詢，常會根據某些不同的分群觀點來將資料作分類。例如交易資料中的「產品分類」或是「單據種類」，使用者經常會希望將這些欄位當作查詢條件，讓他們可以找出相同產品分類或是單據分類的資料。</p>
<p>然而，實際上的查詢作業需求可能會比較複雜，也就是使用者可能會需要在一次資料查詢的結果，希望把不同資料分類，但彼此的分類之間卻同屬於相同分類群組的資料顯示在一起。我們可以新增資料分類群組的欄位，並依據資料分類來設定其分類群組的內容。這種作法在資料查詢方面的實作上非常簡便，但缺點則是要在資料的維護上額外花費一些成本。</p>
<p>因為這樣的設計會造成資料欄位的遞移相依，違反<a href="http://en.wikipedia.org/wiki/Second_normal_form">資料正規化的原則</a>。當資料分類內容修改時，分類群組的內容也要跟著修正；而實際上很可能會因為程式的瑕疵或系統出現異常現象而造成資料不完整或缺乏一致性的風險。</p>
<p>而且，很多依據分類群組集中查詢的需求往往是在專案後期才提出來，如果開發者並不願意因此改動資料庫結構，要如何在不增加群組欄位的情況下，實作出依據分類群組集中查詢的功能呢？</p>
<p>答案當然是將分類群組欄位化成對應的查詢語法或群組欄位的顯示內容，這樣會讓查詢資料的實作程式變得複雜些。因為程式實作者除了必須知道資料的來源之外，尚須瞭解群組欄位與資料分類之間如何轉換。而當相同的查詢需求散布在系統各個程式之間時，就會增加程式開發的複雜度與出錯機率。尤其使用者可能會視作業方式來增加分類群組的需求，開發者應該如何運用設計的彈性，以降低程式開發沒有必要的複雜度呢？</p>
<p>同人在此提出一種設計手法，運用彈性分類群組策略的設計來解決以上的問題，請參考如下所示的設計模型。<br />
<img src="http://www.lifeparty.idv.tw/blog/wp-content/uploads/2008/10/groupstrategy.png" style="max-width: 800px" /><br />
此設計模型定義了分類群組類別 TypeGroup，運用此類別可產生新的分類群組，指定群組名稱、說明、以及其分群依據的資料分類欄位。而為了將分類群組的查詢及資料顯示演算法封裝起來，我們運用 <a href="http://en.wikipedia.org/wiki/Strategy_pattern">Stratrgy Pattern</a> 封裝了 getFindCriteria()、及 getGroupValue() 等操作界面成為 GroupStrategy 界面，而將查詢及顯示分類群組的工作交由實作此界面的類別。</p>
<p>於是，我們便可抽離查詢及顯分類群組的操作，將它們委託給隱藏於內的策略物件。而此策略物件則可視實際的需要加以抽換，而主要的資料查詢程式則不會受到群組演算法的更動而受到影響。</p>
<p>當然策略物件我們可運用 <a href="http://en.wikipedia.org/wiki/Abstract_Factory">Abstract Factory</a> 產生，但更簡便的作法則是可交由 <a href="http://en.wikipedia.org/wiki/Spring_framework">spring framework</a> 來管理其策略物件的生命期。如此，彈性分類群組策略的設計更能夠享受到元件化開發的好處了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/389/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>可用性與可靠性的設計考量</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/361</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/361#comments</comments>
		<pubDate>Thu, 07 Aug 2008 10:28:51 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[問題解決]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[生活感觸]]></category>
		<category><![CDATA[職場]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/361</guid>
		<description><![CDATA[除了軟體功能之外，開發者還必須兼顧性能的設計，其中當然包括兼顧操作的人性化需求外，還必須思考如何減少因為設計不良而造成操作錯誤的機會。]]></description>
			<content:encoded><![CDATA[<p>最近有位程式開發者 P 君展示他所開發的系統功能，那是供使用者維護代碼的操作界面。由於之前原程式開發者認為按照規格不容易做到，因此委由另一位程式設計師改用折衷的設計方式，希望能夠同時達到使用者便於操作及容易開發兩個目的。</p>
<p>系統操作界面總共只有三個欄位，同時考慮到使用者操作的便利性，因此我將此界面規格設計為可直接修改顯示在資料清單上的資料、並在刪除時提供勾選的方式讓使用者可進行多筆資料刪除；而在新增資料方面，我認為使用者會需要看到清單上的資料，因此設計直接在清單底端直接輸人新增資料。</p>
<p>但這樣的設計，原先負責開發的 D 君認為開發困難度較高。因此專案經理就與我討論可否折衷一下，請另一位開發者P君改用既能滿足輸入資料與顯示清單在同一個畫面，又能降低開發的困難度。我認為如果能夠做到那當然很好，但強調必須符合可用性的需求；也就是為了操作簡便，必須要能夠讓使用者同時修改或刪除整批資料。</p>
<p>然而，當同人看到 P 君展示的系統時，卻發現他所開發出的軟體，不但無法完全達到我先前所提到的需求，而且還有無法符合可靠性需求的問題。P 君把新增、修改及刪除的功能放在資料輸入的表單中，卻為了同時容納這三個功能的需要，讓每個欄位都可讓使用者輸入。顯然，這是個粗糙的設計。因為這讓使用者有可能在修改或刪除資料時，改動到他不該更動的欄位而使系統產生難以預料的問題。</p>
<p>同人對這樣的設計實在感到不可思議，但 P 君與負責系統設計的 J 君卻認為這樣的設計很合理。我告訴他們以系統分析師的觀點來看，是不可能接受這樣的設計的。因為我很清楚，使用者是不會認為這樣的設計是合理的，而是會認為怎麼會那麼不專業，設計出這種「笨」系統。</p>
<p>如果使用者在修改資料或刪除時，因為無意或無知，改動到他不該改動的欄位，因而讓系統發生使用者無法預期的結果，難道系統開發者不該避免這樣的錯誤嗎？</p>
<p>這些技術人員可能是認為不用考慮那些意外狀況，那是使用者的問題；但以資訊時代的 PAPA 四大倫理議題<sup>[1]</sup>的正確性（accuracy）來看，設計、操作及管理資訊系統應確保資訊的正確性、真實性及可靠性。</p>
<p>因此除了軟體功能之外，開發者還必須兼顧性能的設計，其中當然包括兼顧操作的人性化需求外，還必須思考如何減少因為設計不良而造成操作錯誤的機會。</p>
<p>同人常聽到開發者常以「那很麻煩」（但還是可以做到吧）、那樣設計很複雜（操作失誤的異常處理就不複雜？）來推託。其實這樣的理由只是告訴我，開發者不願意多想一步，以發展出更容易使用與可靠性的系統，只要可以用就好了嘛！這讓我想到在電視上聽到歌唱比賽評審的講評：「專業與不專業就是只差那麼一點！」真是至理明言呀。</p>
<p>所以，軟體設計專業不是將技術當做孤芳自賞的藝術，而是需要<a href="http://www.lifeparty.idv.tw/blog/archives/335">同理使用者心聲</a>的理性。否則開發者在期待使用者要同樣用技術來看問題時，卻只能得到使用者認為系統「它抓不住我」的埋怨。</p>
<p>其實那也只能怪開發者不懂得「永遠為使用者多設想一步」的設計專業，使用者並不是傻瓜，但聰明的設計卻是會連傻瓜也不會弄錯的。因為就算使用者是傻瓜，也有聰明的設計幫忙引路，這正是「他傻瓜，我聰明」的道理呀。</p>
附註：
&nbsp;<hr/><ol class="footnotes"><li id="footnote_0_361" class="footnote">Mason, Richard O.,(1986) “ Four Ethical Issues of the Information Age.” MIS Quarterly, Vol 10., No. 1, March 1986, pp. 4-12.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/361/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>系統自動作業的主要參與者</title>
		<link>http://www.lifeparty.idv.tw/blog/archives/353</link>
		<comments>http://www.lifeparty.idv.tw/blog/archives/353#comments</comments>
		<pubDate>Thu, 10 Jul 2008 10:13:53 +0000</pubDate>
		<dc:creator>jim yeh</dc:creator>
				<category><![CDATA[分析設計建模]]></category>
		<category><![CDATA[利害關係人]]></category>
		<category><![CDATA[設計原則]]></category>

		<guid isPermaLink="false">http://www.lifeparty.idv.tw/blog/archives/353</guid>
		<description><![CDATA[許多採用使用案例建模方法進行需求分析的開發者，對於不需要使用者介入的系統自動作業，通常會用工作排程者來當成使用案例的主要參與者。讓系統工作排程來啟動使用案例以與系統互動，這對系統開發者而言，可以讓他們了解系統應該如何與外界互動以完成系統所提供的功能，這樣看起來是可行的需求塑模手法。
不過，工作排程者牽涉到系統設計的觀點，如果我們不想讓需求相依於系統設計，那麼我們就必須將工作排程者變成更共通而抽象化的概念。工作排程者的抽象化概念是什麼呢？我們不難理解，工作排程者也只不過是實現時間概念的具體設計。因此，照理時間才是系統自動作業的主要參與者。
然而，當我們把時間當成系統自動作業的主要參與者時，我們會遭遇到另一個問題。那就是基於黑箱的角度來看系統，使用案例應該是以使用者或外部系統來看系統，瞭解如何系統與互動而達到特定目的或產生價值。很顯然地，系統自動作業並沒有辦法為「時間」達到任何的目的或創造價值。
換句話說，我們並未找到系統自動作業的真正使用者，即使我們把工作排程者當成使用者，以使用系統的組織觀點來看，我們將會無法了解系統自動作業和組織目標的達成有何關係，而遺漏了相當重要的使用案例觀點（use case view）。
同人常觀察到許多專注技術的開發者並不重視使用案例觀點，而是在意使用案例如何直接轉換成實際的設計與程式碼。當然，在專案時程及資源的限制之下，強調軟體設計的實用性以及開發的效率而言，這樣做似乎是無可厚非的。然而，開發者卻往往會忽略使用案例觀點的遺漏是無法用設計觀點（design view）來彌補的。因為開發者的設計能力再強，也很難能夠在命題錯誤的情形下，發展出正確的解答。
因此，我們應該從組織的觀點來看待系統自動作業的主要參與者，而非技術與設計觀點。一般而言，在企業價值鏈中，系統自動作業是用來增進系統的可用性與效率，因此它的使用者其實是系統管理者。他的責任是管理系統組態，例如多久檢查一下資料的狀態來決定該執行接續的作業。如果沒有工作排程者的設計，那麼他必須隔一段時間就檢查資料；有了系統自動作業雖然可以減輕工作負擔，但他仍須維護組態設定，以確保系統運作之正常。
有些開發者會以訊息系統來當成系統自動作業的主要參與者，記得克明兄常用「送信小弟」來隱喻訊息系統這個參與者。不過，從我最近的需求分析的經驗來看，用訊息系統來當成系統自動作業的主要參與者有時候不見得合適。
如果組織並沒有賦予訊息系統明確的角色定位時，它多半會變成某些系統內部的傳訊元件，並不適合拿來當成使用案例的主要參與者；而有明確定位組織角色的訊息系統通常是通訊管理的基礎建設，這時可以把它看做是系統管理者的角色。但系統管理的機制會視對系統要求的性能不同而有所變化，不一定就是訊息系統，所以把訊息系統當成主要參與者，常會使得需求模型顯得缺乏抽象性。
這也讓我們發現到，兼顧使用案例與設計觀點才能讓我們在軟體分析的過程，確實地遵循抽象化的系統分析原則呀。
]]></description>
			<content:encoded><![CDATA[<p>許多採用<a href="http://en.wikipedia.org/wiki/Use_case_model">使用案例建模</a>方法進行需求分析的開發者，對於不需要使用者介入的系統自動作業，通常會用<a href="http://en.wikipedia.org/wiki/Job_scheduler">工作排程者</a>來當成<a href="http://en.wikipedia.org/wiki/Use_case">使用案例</a>的主要參與者。讓系統工作排程來啟動使用案例以與系統互動，這對系統開發者而言，可以讓他們了解系統應該如何與外界互動以完成系統所提供的功能，這樣看起來是可行的需求塑模手法。</p>
<p>不過，工作排程者牽涉到系統設計的觀點，如果我們不想讓需求相依於系統設計，那麼我們就必須將工作排程者變成更共通而抽象化的概念。工作排程者的抽象化概念是什麼呢？我們不難理解，工作排程者也只不過是實現時間概念的具體設計。因此，照理時間才是系統自動作業的主要參與者。</p>
<p>然而，當我們把時間當成系統自動作業的主要參與者時，我們會遭遇到另一個問題。那就是基於<a href="http://en.wikipedia.org/wiki/Black_box">黑箱</a>的角度來看系統，使用案例應該是以使用者或外部系統來看系統，瞭解如何系統與互動而達到特定目的或產生價值。很顯然地，系統自動作業並沒有辦法為「時間」達到任何的目的或創造價值。</p>
<p>換句話說，我們並未找到系統自動作業的真正使用者，即使我們把工作排程者當成使用者，以使用系統的組織觀點來看，我們將會無法了解系統自動作業和組織目標的達成有何關係，而遺漏了相當重要的使用案例觀點（use case view）。</p>
<p>同人常觀察到許多專注技術的開發者並不重視使用案例觀點，而是在意使用案例如何直接轉換成實際的設計與程式碼。當然，在專案時程及資源的限制之下，強調軟體設計的實用性以及開發的效率而言，這樣做似乎是無可厚非的。然而，開發者卻往往會忽略使用案例觀點的遺漏是無法用設計觀點（design view）來彌補的。因為開發者的設計能力再強，也很難能夠在命題錯誤的情形下，發展出正確的解答。</p>
<p>因此，我們應該從組織的觀點來看待系統自動作業的主要參與者，而非技術與設計觀點。一般而言，在企業<a href="http://en.wikipedia.org/wiki/Value_chain">價值鏈</a>中，系統自動作業是用來增進系統的可用性與效率，因此它的使用者其實是系統管理者。他的責任是管理系統組態，例如多久檢查一下資料的狀態來決定該執行接續的作業。如果沒有工作排程者的設計，那麼他必須隔一段時間就檢查資料；有了系統自動作業雖然可以減輕工作負擔，但他仍須維護組態設定，以確保系統運作之正常。</p>
<p>有些開發者會以<a href="http://en.wikipedia.org/wiki/Inter-process_communication">訊息系統</a>來當成系統自動作業的主要參與者，記得<a href="http://www.kenming.idv.tw">克明兄</a>常用「送信小弟」來隱喻訊息系統這個參與者。不過，從我最近的需求分析的經驗來看，用訊息系統來當成系統自動作業的主要參與者有時候不見得合適。</p>
<p>如果組織並沒有賦予訊息系統明確的角色定位時，它多半會變成某些系統內部的傳訊元件，並不適合拿來當成使用案例的主要參與者；而有明確定位組織角色的訊息系統通常是通訊管理的基礎建設，這時可以把它看做是系統管理者的角色。但系統管理的機制會視對系統要求的性能不同而有所變化，不一定就是訊息系統，所以把訊息系統當成主要參與者，常會使得需求模型顯得缺乏抽象性。</p>
<p>這也讓我們發現到，兼顧使用案例與設計觀點才能讓我們在軟體分析的過程，確實地遵循<a href="http://en.wikipedia.org/wiki/Abstraction_%28computer_science%29">抽象化</a>的系統分析原則呀。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lifeparty.idv.tw/blog/archives/353/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
