顯示具有 design 標籤的文章。 顯示所有文章
顯示具有 design 標籤的文章。 顯示所有文章

2018年3月28日 星期三

關於Server時區的問題

工作上經常會接觸很多部Server,而每個Server雖然時間都是接近或者一樣的,但時區設定可能會各有不同,尤其是面對國際的Server更是。因此如果在開發Server程式而需要用電腦時間來執行某些工作的時候,就要小心時區兼容的問題。即使你的程式是以香港時間為準,千萬不要假設了你的程式一定會在香港時區的電腦運行

當你在程式內想取得電腦現在的時間時,需要小心你取得的是本地時區的時間 (Localtime),還是世界協調時間 (UTC/GMT,雖然在科學的定義上UTC和GMT有少許不一樣,但一般在寫程式上會視兩者為同一物)。寫程式很容易犯的錯誤就是沒有考慮到底取得了哪個時區的時間。如果運行程式的電腦(尤其是伺服器)有機會是處於不同時區,而你想取得的是準確的時間(尤其是要計算或顯示時、分、秒),那就千萬別拿本地時區時間。

假設我們身處香港,時區是UTC+8,有一個伺服器程式會在每天香港時間晚上六點執行一次任務。如果這個程式在一部UTC(+0)的電腦上執行,就會變成香港時間凌晨兩點才執行。也許你會覺得只要能夠自行設定這個時間而不是寫死(hardcode)在程式內,再看看目標電腦是哪個時區而更改設定,就能解決這個問題。但一來我們有時候並不知道目標電腦的時區是甚麼,二來有一些程式邏輯可能假定了執行任務時的日期,當出現跨日期的情況就更難處理。

其實最簡單的方法就是永遠只取得世界協調時間,再將其調整成你自己想要的時區。例如在香港的話,取得世界協調時間後直接加上八小時。這樣無論電腦是設置成哪個時區,我們都能取得正確的香港時間。當然視乎情況也可以一律只使用UTC時間作準

C++程式碼例子:
//Get UTC+8 Time (Windows MFC library)
CTime ctNow = CTime::GetCurrentTime();
tm tmHongKongTime;
(ctNow  + CTimeSpan(0, 8, 0, 0)).GetGmtTm(tmHongKongTime);
return tmHKT;

// Get UTC+8 Time (Standard library)
time_t t = time(NULL);
struct tm *tmHKT = gmtime(&t); //still GMT, not yet HKT
tmHKT->tm_hour += 8; //add 8 to GMT
mktime(tmHKT); //recompute the values to make it valid
return tmHKT;

// Print time
printf("Now is %d/%d/%d %02d:%02d:%02d HKT",
 tmHKT->tm_year + 1900, tmHKT->tm_mon + 1, tmHKT->tm_mday,
 tmHKT->tm_hour, tmHKT->tm_min, tmHKT->tm_sec);
c#程式碼例子:
C#的DateTime可以直接在上面設定時區,然後無論是Property還是Print出來的時間都會自動幫你調成該時區的時間:
// Assume now is 2018:08:23 16:27:04 Hong Kong Time

// UTC Time: 2018-08-23 08:27:04 Utc 8 636706096247972300
DateTime dtUTC = DateTime.UtcNow;
Console.WriteLine("{0} {1} {2} {3}", dtUTC, dtUTC.Kind, dtUTC.Hour, dtUTC.Ticks);

// Local Time (now Hong Kong Time): 2018-08-23 16:27:04 Local 16 636706384247972300
DateTime dtLocal = dtUTC.ToLocalTime();
Console.WriteLine("{0} {1} {2} {3}", dtLocal, dtLocal.Kind, dtLocal.Hour, dtLocal.Ticks);

// Absolute Hong Kong Time (ignore machine timezone): 2018-08-23 16:27:04 Unspecified 16 636706384247972300
DateTime dtHKT = TimeZoneInfo.ConvertTimeFromUtc(dtUTC, TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
Console.WriteLine("{0} {1} {2} {3}", dtHKT, dtHKT.Kind, dtHKT.Hour, dtHKT.Ticks);
JavaScript程式碼例子:
//JavaScript
function GetAbsoluteHKTime()
{
    var now = new Date();
    now.setUTCHours(now.getUTCHours() + 8);
    return {
        "year" : now.getUTCFullYear(),
        "month" : now.getUTCMonth(),
        "date" : now.getUTCDate(),
        "hour" : now.getUTCHours(),
        "minute" : now.getUTCMinutes(),
        "second" : now.getUTCSeconds()
    };
}

var j = GetAbsoluteHKTime();
console.log("Now is " + j.year + "/" + j.month + "/" + j.date + " "
+ ('0' + j.hour).slice(-2) + ":" 
+ ('0' + j.minute).slice(-2) + ":" 
+ ('0' + j.second).slice(-2) + " HKT");



C++程式碼例子:(以下代碼嚴格來說並不正確,因為沒有跟標準,CTime的GetHour()之類的函數會回傳本地時區的時間,如果想要拿UTC時間就要用GetGmtTm)

// Get UTC+8 Time (Windows MFC library)
SYSTEMTIME st;
GetSystemTime(&st);
CTime ctHongKongTime = CTime(st) + CTimeSpan(0, 8, 0, 0)

2017年7月24日 星期一

類別成員函數 (Class member variables) 常見的分類

在編寫Class的時候需要define很多member variables(又叫fields),供Class的member functions(又叫method)使用。但經常會看到一些比較大的Class被人加了許多member variables(從數十個到上百個都有),而且都是亂排,只看到一堆排列得沒有意義的variable會讓人很難了解這個class想做甚麼。寫Class寫多了見多了的時候會覺得其實member variable來來去去也是那幾個作用,所以我自己寫Class時通常會把member variable分成下面四類,同類的會放在一起,方便以後查看,也能提醒自己別漏了initialize某些variable:

Data Containers

裝著一些程式所需要用到的資料,尤其是一些複雜的資料結構(Data Structure),如map、list、queue。

Resources

一般是一些來自Library Class 的 Instance,當中常需要用到一些作業系統的API,或者IO用。
例子:

  • Socket (供UDP/TCP連接用)
  • Event
  • Critical section
  • File

States

用來記錄目前程式的狀態。這些variable一般都需要在Constructor initialize,避免之後第一次存取的時候出錯。
例子:

  • 上一次執行某程序的時間
  • 現在程式運行到哪一個階段 (Stage)
  • 目前是否連接著伺服器

Config

程式一開始的時候會載入的設定,一般來説之後較少或不會更改

2017年6月1日 星期四

如何寫出易讀的程式碼?

寫程式除了自己寫出新的程式碼外,也常常需要修改其他人寫的程式碼,但見過許多許多不同的程式碼後,體會到不同人寫出的程式碼的風格差異大,質素參差,不整齊,又沒有comment。這樣的程式碼卻往往留存下來的原因就是因為大家都看不懂它到底想做甚麼,所以不敢碰它,怕一旦改了就會導致程式錯誤。。尤其是程式的作者往往已經不在了,又或者連他自己也不記得自己當時在寫甚麼。

個人認為Let code speak for themselves這一點是非常重要的,即是讓其他人單看程式碼就能看得懂,不用問作者,不用依賴其他文件。而且寫得好且漂亮的Code也能讓人讀起來沒那麼辛苦(起碼會讓人想讀),提高生產力。

以下是我自己會使用的一些原則,盡量讓自己的程式碼能讓其他人看得懂:

1. 直接了當,沒有comment也能看得懂

最好的code是用variable和function名稱本身解釋一切,你應該做的最多就是解釋「為何要這樣做」,而不是「在做甚麼」。當然把程式碼分成一段段再用comment來大致解釋那一段的用途會更好。

2. 一個屏幕顯示整個function

控制function的長度,最好在一個屏幕便能顯示function的所有(或大部分)內容
如果開始感覺function的長度超出你可以一眼理解和控制的範圍,請把部分內容拆成另外一個function,並用容易理解的命名,這樣便能讓人容易掌握這個function的input和output。

3. 用scope來限制variable的影響力

很多variable的用途是非常短暫的,可以利用scope來清楚標明variable的影響力,這樣別人便能容易理解這個variable只會在某小段區域存在,不會被後面用到。 例如下面的code,nLineLen只是用來設定szLine的,所以用完便應捨棄:
char* szLine = NULL;
{ 
    const int nLineLen = 100;
    szLine = new char[nLineLen + 1];
    memset(szLine, 0, nLineLen + 1);
}
不過要留意的是,有些語言以及比較舊的compiler會無視scope。

4. 別亂comment code

經常看見有人會把一大段code都comment掉,也許這段code只是用來debug,又或者是因為程式要求有改過所以已經不適用但又不捨得刪掉,結果讓這種commented code越來越多,影響閱讀。

如果這段code是不需要的,請無情刪了它,又或者是把舊版本放進Version Control之後再在新版本刪了它。如果因為某些原因真的不捨得移除,就起碼加上註解,解釋這段code為甚麼會被comment。

5. 幽默

如果這段程式碼不需要提交給客戶的話,便可大膽一點,comment一些有趣或者搞笑的事情,或者是用較口語化的語氣來說明一些程式碼的功用,起碼讓人看起來能夠會心一笑,不會覺得沉悶。