Mars's Blog

程式碼追蹤定位

一、說明

程式的開發及維護時,對程式掌握度的高低對開發維護的效率與質量影響很大,但我們又不可能將做過的程式永遠記得牢牢的,總會有其他人的改動,總會接觸其他人編寫的程式,所以個人對程式的閱讀追蹤能力很大一部份代表個人實力,在此介紹一些php web程式的閱讀追蹤方式。

二、程式結構與追蹤

2.1 web使用模型

網頁使用流程可解析成(MVC):

  1. User輸入網址
  2. Browser對Controller要求View頁面
  3. View對Controller發出ajax請求
  4. Controller透過Model存取Database
  5. Controller將資料ajax回應給View
  6. View渲染畫面給User

從流程中可看出,View是web系統的起點跟終點

2.2 字串搜尋方式追蹤

適用:想要找到畫面內容所在程式碼

  • 以 Yii 2 basic 預設頁面為例,假設這個頁面我從未接觸過

  • 我想將「Congratulations!」改成「Congratulations-trace」時,可直接從頁面原始碼中的目標附近找一個不太可能重複的字串,如「class=”jumbotron”」

  • 將字串「class=”jumbotron”」貼到IDE的全專案搜尋中尋找,如字串選得好,找到的可能目標會非常少,很容易確認目標。

  • 修改後查看狀況,完成!

使用本方法,需注意不要選可能是組合字、變數等字串,大略可用tag, id, class, style名稱/值 為搜尋字串

2.3 依流程追蹤

適用:想要找到畫面內容所在程式碼

  • 從網址列可以得知目標是Controller: site, Method: index

  • 打開目標程式,可以找到目標view檔案

  • 打開目標View,找到目標,修改後查看成果。完成!

使用本方法,需熟知framework運作流程及相關函式。

2.4 插行號/檔名/字串方式追蹤

適用:

  • 狀況一:當程式錯誤後會沒有任何輸出時
  • 狀況二:當想知道有哪些判斷式被執行時

狀況一:當程式錯誤後會沒有任何輸出時

如果要確定在執行哪段程式時發生錯誤,可在可能的錯誤點前插行號。

下例假設資料處理掛了且沒有錯誤輸出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 預設輸出
$opt = [
'code' => 200,
'message' => 'OK',
];

try {
// 取得輸入資料
$post = $input->post();
echo __LINE__."\n";
// 初始化
$users = \app\service\Users::getInstance();
$rule = \app\service\Rule::getInstance();
$timeoff = \app\service\Timeoff::getInstance();
$punch = \app\service\Punch::getInstance();
$attend = \app\service\Attendance::getInstance();

echo __LINE__."\n";
// 輸入資料整理
$userData = $users->prepare($post);

echo __LINE__."\n";
// 資料驗証
if (! $rule->validate($userData)) {
echo __LINE__."\n";
throw new Exceptioh('Input error', 400);
}

// 取得相關資料
$timeoffData = $timeoff->get($userData['id'], $startDate, $endDate);
$punchData = $punch->get($userData['id'], $startDate, $endDate);

echo __LINE__."\n"; // <=== 因資料處理掛了,所以本行為最後一個輸出的行號
// 資料處理
$attendData = $attend->workStatus($timeoff, $punch, $startDate, $endDate);

echo __LINE__."\n";
// 寫入資料
$attend->write($attendData);
} catch (Exception $e) {
// 錯誤處理
$opt = [
'code' => $e->getCode(),
'message' => $e->getMessage(),
];
}

// 輸出結果
return $output->json($opt);

上例可看出:

  • 最後一個輸出行號在資料處理之前
  • $rule->validate($userData)驗証有通過,因為沒有看到丟出例外前的那個行號
  • 當然如果不嫌麻煩,也可以不用__LINE__行號(魔術常量),而是自己定義輸出內容

狀況二:當想知道有哪些判斷式被執行時

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 取得$userID在$startDate至$endDate的出勤資料
$attendances = $attend->get($userID, $startDate, $endDate);


// 本行號用來確定有開始動作
echo __LINE__."\n";

// 出勤資料-落點 - 重複打卡、無效卡、缺卡、遲到、早退等狀況,會依打卡先後順序及落點判斷
foreach ($attendances as $key => $att) {

// 本行輸出為:定位loop迴圈數
echo '$key = '.$key."\n";

// 本行輸出為:印出迴圈內容
//echo '$att = '. var_export($att, true) ."\n";

// 本行號用來確定loop開始
echo __LINE__."\n";

try {
if ($att <= $workStart) {
// 上班前
echo __LINE__."\n";
// 略...
} elseif ($workStart < $att && $att < $restStart) {
// 上班後至休息前
echo __LINE__."\n";
// 略...
} elseif ($restStart <= $att && $att < $restEnd) {
// 休息時間
echo __LINE__."\n";
// 略...
} elseif ($restEnd < $att && $att < $workEnd) {
// 休息後至下班前
echo __LINE__."\n";
// 略...
} elseif ($workEnd <= $att) {
// 下班後
echo __LINE__."\n";
// 略...
}
} catch (Exception $e) {
// 例外處理
echo __LINE__."\n";
}

// 本行號用來確定loop結尾
echo __LINE__."\n";
}

// 本行號用來確定結尾
echo __LINE__."\n";
  • 從輸出的 行號定位loop迴圈數印出迴圈內容 可協助判斷程式碼執行狀況。
  • 當然如果不嫌麻煩,也可以不用__LINE__(行號魔術常量),而是自己定義輸出內容

2.5 追蹤呼叫歷程

適用: 想知道本函被呼叫的過程時,可使用函式debug_backtrace()取得呼叫歷程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Home extends CI_Controller
{
public function index()
{
// $options = 2;時顯示較少資訊 ; $limit 為向上追蹤層數,0為不限
$debug = debug_backtrace($options = 2, $limit = 0);
print_r($debug);
exit;
}
}

// 輸出結果:
// Array
// (
// [0] => Array
// (
// [file] => /var/www/html/system/core/CodeIgniter.php
// [line] => 532
// [function] => index
// [class] => Home
// [type] => ->
// )
//
// [1] => Array
// (
// [file] => /var/www/html/index.php
// [line] => 318
// [args] => Array
// (
// [0] => /var/www/html/system/core/CodeIgniter.php
// )
//
// [function] => require_once
// )
//
// )

2.6 查看輸入輸出資料變化

適用:輸入輸出值錯誤 (沒有語法錯誤)

1
2
3
4
5
6
7
8
9
// 查看參數值
echo '$id = '. $id ."\n";
echo '$date = '. $date ."\n";

// 資料取得函式
$data = $obj->getData($id, $date);

// 查看資料內容
echo '$data = ' . var_export($data, 1) . "\n";
  • 觀察輸入輸出值是否符合預期