Code Complete II《軟體建構之道 2》#12 讀書心得與整理

  1. Code Complete II《軟體建構之道 2》#12 讀書心得與整理
    1. 第十二章 主要資料型別 Fundamental Data Types
      1. 12.1 數字概述 Numbers in General
      2. 12.2 整數 Integers
      3. 12.3 浮點數 Floating-Point Numbers
      4. 12.4 字元及字串 Characters and Strings
      5. 12.5 布林變數 Boolean Variables
        1. 要用錯,很難;善用,程式會變美
      6. 12.6 列舉型別 Enumerated Types
        1. 如果你的語言不支援列舉 If Your Language Doesn’t Have Enumerated Types
      7. 12.7 常數 Named Constants
      8. 12.8 陣列 Arrays
        1. 在c語言中
      9. 12.9 創造自有型別(型別別名) Creating Your Own Types
        1. 為什麼自行構築型別的範例要用Pascal和Ada? Why Are the Examples of Creating Your Own Types in Pascal and Ada?
        2. 構築你的UDT原則 Guidelines for Creating Your Own Types

Code Complete II《軟體建構之道 2》#12 讀書心得與整理

原文連結: https://darkblack01.blogspot.com/2013/04/code-complete-ii-212.html
移植時的最後更新日期: 2013-05-15T16:42:06.102+08:00

第十二章 主要資料型別
Fundamental Data Types


12.1 數字概述
Numbers in General

以下是使用數字較不易出錯的提醒:
  • 避免使用「magic number」
  • 必要時,可以hard code 0和1(本身有隱喻)
  • 遞增、遞減、迴圈初始值
  • 除法小心除0
  • 讓型別轉換更明顯
  • 避免混合型別比較
  • 注意編譯器warning




12.2 整數
Integers

除法
    最簡單的方法:重新組識運算式
檢查溢位
    如果你的整數會在數年間穩定成長,就要考慮進來
檢查中繼結果的溢位
    算式最後的結果不是你唯一要擔心的數字

12.3 浮點數
Floating-Point Numbers

精確度到小數點後7位
避免數量級差距很大的數字做加減運算
    排序這些數字,從最小的開始運算,減少誤差
避免相等比較
    相同的浮點數不一定以相同的方式保存在記憶體
    替代方案:用範圍的true, false來取代==
//Java Example of a Bad Comparison of Floating-Point Numbers
double nominal = 1.0;
double sum = 0.0;
for ( int i = 0; i < 10; i++ )
um += 0.1;if ( nominal == sum )
System.out.println( "Numbers are the same." );
else
System.out.println( "Numbers are different." ) ;
將這段程式,改成下面的寫法
//Java Example of a Routine to Compare Floating-Point Numbers
double const ACCEPTABLE_DELTA = 0.00001;
boolean Equals( double Term1, double Term2 )
{
if ( Math.abs( Term1 - Term2 ) < ACCEPTABLE_DELTA )
return true;
else
return false;
}
處理四捨五入的錯誤
  1. 使用double
  2. 使用BCD變數
    • 缺點:慢
  3. 用int來替代float
    • 讓全部的運算都乘上相同的倍數,再除回來。
      ex: 10.123×1000 = 10123
檢查語言及函式庫對特定資料型別的支援

12.4 字元及字串
Characters and Strings

避免不斷重覆出現的magic characters和strings
    使用字串常數,統一管理
    使用全域變數
    統一管理,方便翻譯成不同語言
    限制記憶體開發環境,獨立字串儲存空間
小心off-by-one錯誤
    字串可以像陣列一樣索引化,有超出'\0'的問題要小心
了解您的語言和開發環境如何支援Unicode
開發初期就決定程式支援語系
選擇適合的編碼
    (只用英文)→ASCII→ISO8859→(用英文以外的語系)→Unicode
決定一致的字串的型別轉換策略

C語言的字串Strings in C
注意字串指標和字元陣列的差異
小心字串使用等號
    正確的用法:使用strcmp(), strcpy(), strlen()
    等號通常是指某種指標的錯誤
    在c語言中看見這樣的陳述式
    StringPtr = "Some Text String";
    不是將內容複製到StringPtr,而是將指標指在第一個'S'。
使用前綴詞的命名原則分辨
    ps = ptrString
    ach = arrayChar
字串長度宣告成constant+1
    很容易忘記constant長的字串,要宣告constant+1的字元陣列
    解決方式:用字串常數取代字元陣列的用法。多宣告一個byte,避免使用最後一個byte。

用null初始化字元陣列,避免在最後忘記放null
    靜態陣列宣告,直接放初始值 = { 0 };
    動態陣列宣告,用calloc取代malloc
      malloc    不會初始化為0
      calloc    會初始化為0
使用字元陣列取代c的陣列指標
    用字元陣列存放所有字串變數,編譯器也可以在出錯時給你警告

12.5 布林變數
Boolean Variables

要用錯,很難;善用,程式會變美

簡化複雜判斷
如果if (陳述式) 的陳述式是一團泥,陳述式的判斷拆開成有意義的布林變數,可增加程式碼可讀性
(將布林表示式賦序一個變數,使判斷含意變明顯)
//Java Example of Boolean Test in Which the Purpose Is Unclear
if ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) || ( elementIndex == lastElementIndex ) )
{
...
}
改成
//Java Example of Boolean Test in Which the Purpose Is Clear
finished = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) );
repeatedEntry = ( elementIndex == lastElementIndex );
if ( finished || repeatedEntry )
{
...
}
判斷的意思會更容易浮到字面上

需要的話,創造自己的布林型別
C++、Java、Visual Basic都有bool的型別,但是像c這種常見的語言卻沒有。
//C Example of Defining the BOOLEAN Type
typedef int BOOLEAN; // define the boolean type
本書原文並無繁簡中的這個例子
//C Example of Defining the BOOLEAN Type Using an Enum
enum Boolean {
True = 1,
False = (!True)
};


12.6 列舉型別
Enumerated Types

是一種允許在物件的類別包含的每個資料成員用英文描述的資料
Public Enum Color
Color_Red
Color_Green
Color_Blue
End Enum
Public Enum Country
Country_China
Country_England
Country_France
Country_Germany
Country_India
Country_Japan
Country_Usa
End Enum
Public Enum Output
Output_Screen
Output_Printer
Output_File
End Enum
增加可讀性
    if chosenColor = 1
    寫得更有可讀性,可改成
    if chosenColor = Color_Red
    在此出現只有繁簡中版才有的例子,而原文版沒出現的例子
    但因為之前VB的例子已具代表性,所以就不在此介紹

增加可靠度
    編譯器會檢查使用列舉型別的變數,只允許該變數使用該列舉的成員。
輕鬆新增/移除新項目
    列舉對應的數字,並不需要管它,在新增移除列舉成員項目時,程式碼只要管「英文」部份的正確性,而所對應的數字會在編譯時重新調整
列舉可當作布林變數的替代方案
    將true/false或1/0命名成英文,成為有意義明顯的變數。warning
列舉成員命名為ELSE,可當作無效值(case中的default,if中的else)

列舉的第一項和最後一項,當作迴圈控制變數使用
    enmu trafficLightColor{ ColorBegin = 0, Red = 0, Green, Yellow, ColorEnd };
    for (int i = ColorBegin; i < ColorEnd; ++i)
    //...
列舉、陣列、迴圈互相搭配使用(補充)
    VB的索引,是以1開始,在此不適用,C、C++、Java都適用於這種方式。
    陣列宣告時,中括號內要填入的數字往往代表的是總數。
    在迴圈計數時,計數器的範圍,往往設定成「小於總數」。
    在列舉宣告時,如果這個列舉的使用要配合迴圈回陣列,即可使用這種方式。
    enmu trafficLightColor{ TC_Red = 0, TC_Green, TC_Yellow, TC_Total };
    //用在陣列
    int something[TC_Total ];
    //用在迴圈
    for (int i = 0; i < TC_Total; ++i)
    //...
列舉的第一個項目保留為無效
    (因為第一個項目可以訂為0)
定訂一致的列舉用法(第一項的命名是無效?還是第一項?那最後一項呢?)

注意全指定值列舉,在迴圈使用
    enum Color
    {
    Color_InvalidFirst = 0,
    Color_Red = 1,
    Color_Green = 2,
    Color_Blue = 4,
    Color_InvalidLast = 8
    };
    for (int i = Color_InvalidFirst; i < Color_InvalidLast ;++i)
    //...
    此例的3, 5, 6, 7就是無效值。

如果你的語言不支援列舉
If Your Language Doesn’t Have Enumerated Types

像Java,可以使用類別,或者用全域變數,都可以達到相同的效果
// Java Example of Simulating Enumerated Types
// set up Color enumerated type
class Color
{
private Color() {}
public static final Color Red = new Color();
public static final Color Green = new Color();
public static final Color Blue = new Color();
}
// set up Country enumerated type
class Country
{
private Country() {}
public static final Country China = new Country();
public static final Country England = new Country();
public static final Country France = new Country();
public static final Country Germany = new Country();
public static final Country India = new Country();
public static final Country Japan = new Country();
}
// set up Output enumerated type
class Output
{
private Output() {}
public static final Output Screen = new Output();
public static final Output Printer = new Output();
public static final Output File = new Output();
}


12.7 常數
Named Constants

參數化程式,讓軟體更軟。
宣告時使用常數
    任何一種可能變化之事物進行集中控制的技術,是減少維護工作量的好技術
避免字面表示常數,即使是安全的
    For i = 1 To 12
    profit( i ) = revenue( i ) – expense( i )
    Next
    上面這個例子,12是代表一年的十二個月嗎?
    如果是,改成下列的寫法是不是比較好?
    For i = 1 To NUM_MONTHS_IN_YEAR
    profit( i ) = revenue( i ) – expense( i )
    Next
    那索引用的i 是不是也可以給予更有意義的變數名稱?
    For month = 1 To NUM_MONTHS_IN_YEAR
    profit( month ) = revenue( month ) – expense( month )
    Next
    嗯!現在看起來,程式似乎是可以了。
    那,如果把1也替換掉,要怎麼改寫呢?
    用列舉的方式將1月到12月的寫法取代掉。

    For month = Month_January To Month_December
    profit( month ) = revenue( month ) – expense( month )
    Next
    這樣就變成全英文的程式碼了,可以表達的意思更加明確。
使用適當作用域的變數,來模擬常數
    像Java這種不支援列舉的語言,使用區域作用域→類別作用域→全域作用域的順序來運用常數。
一致的使用常數
    不要一邊使用常數變數,一邊使用將數字寫死的程式寫法,要改也不是修改一處改全部,這是很危險的事。


12.8 陣列
Arrays

陣列是一組相同資料型別的項目,可使用索引直接存取。
確定所有的索引,都在陣列的邊界內
考慮使用循序存取的容器類別取代陣列
    陣列的「隨機存取」產生可靠性降低的問題。
    循序結構: sets、stacks、and queues
檢查陣列的端點
  • 第一個項目
  • 第一個項目的Off-by-one錯誤
  • 最後一個項目
  • 最後一個項目的Off-by-one錯誤
  • 陣列的中繼項目
多維陣列,少用[i][j],提高可讀性,降低索引互相干擾

在c語言中

使用ARRAY_LENGTH()巨集搭配陣列
    宣告這個
    //C Example of Defining an ARRAY_LENGTH() Macro
    #define ARRAY_LENGTH( x ) (sizeof(x)/sizeof(x[0]))
    下面實作這個
    //C Example of Using the ARRAY_LENGTH() Macro for Array Operations
    ConsistencyRatios[] =
    { 0.0, 0.0, 0.58, 0.90, 1.12,
    1.24, 1.32, 1.41, 1.45, 1.49,
    1.51, 1.48, 1.56, 1.57, 1.59 };
    ...
    for ( RatioIdx = 0; RatioIdx < ARRAY_LENGTH( ConsistencyRatios ); RatioIdx++ );
    ...
    這個方法適合用在一維陣列上,修改陣後,不需修改迴圈上的數字。
    陣列全長/陣列一個單元 = 陣列個數。
只可在宣告的function內使用,function互傳值時無效(補充)
    陣列在宣告時,索引的操作,其實是指標在記憶體中的移動次數,當遇到function呼叫,需要傳遞陣列(其實不可以這樣做)時,都是傳遞指標,再操作指標位移,以達到陣列的操作。


12.9 創造自有型別(型別別名)
Creating Your Own Types

這是語言所能提供的最有力的功能之一
C/C++使用者請善用typedef
其它支援的使用者也請務必使用等效功能

使用時機: 
  1. 表達經緯度,在使用double還是float舉棋不定。
  2. 構築薪資系統,員工名字最多30個字元,但是有些員工還是會超過
  3. 結合自我構築型別及資訊隱藏的方法
創造自己型別參考的原因:
  1. 修改更容易
  2. 修改的資訊集中在一個地方
  3. 讓編譯器可以檢查
  4. 彌補語言的弱點

為什麼自行構築型別的範例要用Pascal和Ada?
Why Are the Examples of Creating Your Own Types in Pascal and Ada?

    Pascal和Ada和恐龍一樣,已經在走向滅亡。取代庫們的語言都更好用,但是在這方面卻反而退步了。
    例子:希望可以確保currentTemperature的賦值,正確性提昇的用法
    currentTemperature: INTEGER range 0..212
    這一句包含了溫度是整數表示
    int temperature;

    再進一步,像這樣的類型宣告
    type Temperature is range 0..212;
       ...
    currentTemperature: Temperature;
    充許currentTemperature賦值的動作受Temperature 的定義所規範。

    以下是重新翻譯的一段:
    「當然,程式設計師可以創造一個Temperature類別來執行相同的語義。(自己刻Ada的自動強制執行語法),但是,從用一行程式碼創造一個簡單的資料型別到用一個類別來創造,是跨了好大一步,許多情況之下,程式設計師會寫創造一個簡單的型別,但不會創造一個類別」

構築你的UDT原則
Guidelines for Creating Your Own Types

    針對真實世界所面臨的問題本身來取名字
    避免內建的資料型別
    不要用相同的意思來重覆定義內建的資料型別
    為可攜性定義替代型別
    考慮用 class取代typedef