16
lớn nhất phải bằng b (return b), nhưng câu lệnh 2 lại trả lại kết quả của hàm là a (return a). Vậy thuật toán này sai, sai ở chính vị trí câu lệnh 2.
Với kết quả chỉ lỗi như Bảng 3.2, người dùng căn cứ vào điểm nghi ngờ từng câu lệnh để sửa chữa chương trình. Những câu lệnh có điểm nghi ngờ lớn sẽ được tập trung xem xét và sửa chữa trước thay vì tìm kiếm lỗi trong toàn bộ chương trình. Bước này có thể thực hiện lại nhiều lần cho tới khi nhận được kết quả đúng. Hoặc nếu vẫn chưa đúng có thể chuyển sang lựa chọn thứ hai, xem gợi ý chương trình như Mục 3.3.
Code Test case
public static int gcd(int a, int b){ (0,0) (0,10) (10,0) (10,15) (15,10)
1 if (a ==0) { x x x x x 2 return a; x x 3 } 4 while (b != 0) { x x x 5 if (a > b) x x 6 a = a - b; x x 7 else x x 8 b = b - a; x x 9 } 10 return a; x x x 11 } Test outcome P F P P P Bảng 3.1. Ví dụ về mã nguồn và ca kiểm thử Code Điểm nghi ngờ
public static int gcd(int a, int b) {
1 if (a ==0) { 0.4 2 return a; 0.7 3 } 0 4 while (b != 0) { 0 5 if (a > b) 0 6 a = a - b; 0 7 else 0 8 b = b - a; 0 9 } 0 10 return a; 0 11 } 0 Bảng 3.2. Kết quả chỉ lỗi
17
3.2.2. Sử dụng thư viện Gzoltar để xác định vị trí lỗi
Bước xác định vị trí lỗi trong bài toán sử dụng thư viện Gzoltar, thư viện này tích hợp công thức tính quang phổ Ochiai. Luồng xử lí định vị lỗi bằng Gzoltar được mô tả bởi Hình 3.4. Bước tiền xử lí đã được mô tả trong Mục 3.2.1 cho kết quả là các tệp tin đã được biên dịch. Đầu vào cho công cụ Gzoltar bao gồm tên các gói mã nguồn lỗi, tên các tệp mã nguồn chứa ca kiểm thử và đường dẫn đến thư mục chứa các tệp mã nguồn sau khi nó được biên dịch.
Hình 3.4. Luồng xử lí định vị trí lỗi
Để đưa các tệp tin .class vào Gzoltar, ta sử dụng các API có sẵn. Mã nguồn 3.1 dưới đây mô tả cách cấu hình các thuộc tính cho Gzoltar. Đầu tiên cần cấu hình đường dẫn tới thư mục chứa các tệp tin class cần kiểm tra lỗi (pathToClassSource) – dòng lệnh 4. Dòng lệnh 6 khởi tạo đối tượng Gzoltar tên gzoltar với tham số đầu vào là đường dẫn pathToClassSource được khai báo ở tên. Vì có nhiều chương trình tương ứng với nhiều bài toán khác nhau, ta cần cung cấp tên class đầu vào cần định vị lỗi bằng phương thức addClassToInstrument() – dòng lệnh 7. Tương tự, dòng lệnh 8 thêm các ca kiểm thử vào để kiểm tra mã nguồn lỗi bằng phương thức addTestToExecute(). Dòng lệnh 9 gọi tới phươngthức run() thực thi việc tính toán điểm nghi ngờ từng câu lệnh theo công thức Ochiai đã được cài đặt sẵn.
Cuối cùng, để lấy ra kết quả điểm nghi ngờ từng câu lệnh dùng phương thức getSuspiciousStatements(). Phương thức này cho phép lấy ra tên class lỗi - getLabel(), dòng lệnh - getLineNumber() cùng với chỉ số nghi ngờ của nó -
Bộ mã nguồn (.java) Biên dịch Danh sách các gói (.class) Danh sách các ca kiểm thử (.class) Gzoltar
18
getSuspiciousness() và hiển thị dưới dạng một chuỗi, ví dụ soNguyenTo.problem1.Problem1@10@0.816 – là kết quả tính điểm nghi ngờ class problem1 của chương trình soNguyenTo tại dòng 10 bằng 0.816. Mỗi class bao gồm nhiều dòng lệnh nên kết quả điểm nghi ngờ mỗi dòng lệnh hiển thị tương tự như trên, hàm for (dòng 10 đến 12) giúp ta thực hiện việc này.
Mã nguồn 3.1. Sử dụng Gzoltar để định vị lỗi 3.3. Gợi ý sửa lỗi chương trình 3.3. Gợi ý sửa lỗi chương trình
3.3.1. Tổng quan về giải pháp gợi ý chương trình
Sau khi được chỉ vị trí lỗi học sinh sẽ tiến hành sửa lỗi. Nếu chương trình vẫn chưa đúng sau khi sửa lỗi, học sinh lựa chọn xem gợi ý sửa lỗi thì kích hoạt mô-đun gợi ý sửa lỗi. Bản chất của việc đưa ra gợi ý sửa lỗi chương trình là tìm chương trình đúng có mức độ tương đồng (tỉ lệ trùng lắp) cao, bài toán này tương đương với bài toán kiểm tra đạo văn, trong đó cũng xác định tỉ lệ trùng lắp giữa các chương trình xem tỉ lệ đó có vượt ngưỡng cho phép hay không. Chương 2 đã trình bày về các công cụ kiểm tra đạo văn để đưa ra lựa chọn tích hợp công cụ JPlag vào giải quyết bài toán. Quy trình thực thi các bước trong JPlag được trình bày trong mục này có tham khảo nội dung trong báo cáo phát hiện tương đồng của công cụ JPlag [24].
Quy trình gợi ý sửa lỗi chương trình sử dụng JPlag được mô tả bởi Hình 3.5. Đầu vào của mô-đun là chương trình lỗi của học sinh và các chương trình đúng khác đã được lưu trong bộ sưu tập. Quy trình thực hiện bao gồm các bước:
19
Bước thứ hai: So sánh các chuỗi Tokens của các cặp chương trình, tính ra tỉ lệ tương đồng cao nhất của các chuỗi Tokens ở các cặp.
Bước cuối: Hiển thị kết quả so sánh tại bước 2 làm gợi ý sửa lỗi cho người dùng.
Hình 3.5. Quy trình thực thi của JPlag 3.3.2. Phân tích chương trình thành chuỗi các Tokens 3.3.2. Phân tích chương trình thành chuỗi các Tokens
Các chương trình cần so sánh được phân tích thành các thẻ Tokens, việc này được hiểu là chuyển các dòng lệnh sang dạng các mã thông báo, các mã thông báo là căn cứ để so sánh ở bước sau. Mã thông báo đặc trưng cho bản chất của câu lệnh chứ không phụ thuộc vào hình thức thể hiện của tên biến, tên hàm. Ngoài ra JPlag cũng quy định các lời gọi phương thức, các ngoại lệ, dòng chú thích hay các dòng trắng sẽ không được gắn mã thông báo, do đây là điểm dễ thay đổi mà không ảnh hưởng đến chương trình. Danh sách các mã thông báo đầy đủ trong phụ lục mã Tokens của công cụ JPlag tại phần cuối của luận văn.
Để hiểu về cách phân tích chượng trình thành các mã thông báo ta xem xét ví dụ Mã nguồn 3.2. Dòng 1, 4, 18, 19, 20 là các dòng chú thích, không được gắn mã thông báo. Dòng 17 là dòng trống, dòng 22 là ngoại lệ cũng bị bỏ qua không gắn mã. Các dòng còn lại được chuyển thành các mã thông báo (sau đây gọi là các Tokens) phản ánh đúng ý nghĩa của câu lệnh. Các dòng lệnh bắt đầu và kết thúc class, hàm, vòng lặp, khởi tạo một đối tượng được chuyển thành các Tokens đặc trưng với BEGINCLASS, BEGINMENTHOD, BEGINWHILE, BEGINIF và ENDCLASS,ENDMENTHOD, ENDWHILE, ENDIF. Dòng 3 khởi tạo lớp được chuyển thành Token BEGINCLASS. Dòng 5 khởi tạo một phượng thức và khai báo 2 biến đầu vào nên được chuyển thành 2 Tokens VARDEF và Token BEGINMENTHOD. Dòng lệnh 6, 7 là bắt đầu câu lệnh while và if được chuyển thành Tokens BEGINWHILE và BEGINIF. Các câu lệnh 8 và 10 là câu lệnh gán giá trị cho biến, vì vậy dù là 2 câu lệnh khác nhau nhưng đều được chuyển thành Token ASSIGN. Tượng tự với những câu lệnh khác.
20
Code Tokens
1 //import your library
2 import java.util.Scanner; IMPORT
3 public class Solution { BEGINCLASS
4 //Type your main code here
5 public static int gcd(int a, int b){ VARDEF, VARDEF,
BEGINMENTHOD
6 if (a==0) BEGINIF
7 return b; RETURN, ENDIF
8 while (a != b) { BEGINWHILE 9 if (a > b) { BEGINIF 10 a -= b; ASSIGN 11 } else { ELSE 12 b -= a; ASSIGN 13 } ENDIF 14 } ENDWWHILE 15 return a; RETURN 16 } ENDMENTHOD 17 18 /** 19 * Main. 20 */
21 public static void main(String[] args) VARDEF, BEGINMENTHOD
22 throws java.io.IOException {
23 int gcd; VARDEF
24 Scanner sc = newScanner(System.in); VARDEF,NEWCLASS,ASSIGN
25 int a = sc.nextInt(); VARDEF, ASSIGN
26 int b = sc.nextInt(); VARDEF, ASSIGN
27 gcd = gcd(a,b); ASSIGN
28 System.out.println(gcd); APPLY
29 } ENDMENTHOD
30 } ENDCLASS
Mã nguồn 3.2. Tách mã nguồn thành các Tokens
Chuyển chương trình thành các Tokens có ý nghĩa lớn trong khâu so sánh tại bước tiếp theo của bài toán này. Việc so sánh kiểu từ với từ giữa các chương trình để tìm chương trình tương đương là không khả thi. Bởi vì học sinh ở giai đoạn mới lập trình thường được yêu cầu sử dụng các chú thích để giải thích đoạn lệnh nên chương trình của mỗi học sinh có nhiều chú thích khác nhau. Ngoài ra do học sinh chưa hoàn thiện thói quen định dạng code nên các lệnh có sử dụng mở đóng ngoặc có thể bị dịch xuống nhiều dòng, thừa dòng trắng giữa các câu lệnh. Việc chuyển mã chương trình
21
thành các mã thông báo giúp giải quyết các vấn đề trên, cho phép tìm ra các chương trình đúng có độ tương tự tốt nhất với chương trình đang cần sửa chữa.
3.3.3. So sánh các chuỗi Tokens
Đầu vào của mô-đun gợi ý chương trình là chương trình lỗi của học sinh và rất nhiều chương trình mẫu khác được lưu trong bộ sưu tập. Vì vậy, việc so sánh được hiểu là so sánh chương trình lỗi với tất cả các chương trình mẫu trong bộ sưu tập. Để thực hiện việc này, chương trình lỗi sẽ so sánh lần lượt với từng chương trình mẫu (Hình 3.6). Với mỗi bộ chương trình lỗi – chương trình mẫu sẽ tính ra tỉ lệ tương đồng, kết quả này được hiển thị cho người dùng là căn cứ lựa chọn chương trình mẫu nào làm gợi ý.
Bước phân tích chương trình thành chuỗi các Tokens như mô tả ở Mục 3.3.2 cho kết quả mỗi chương trình được phân tách thành nhiều Tokens khác nhau, ta gọi là chuỗi các Tokens. So sánh hai chuỗi Tokens trong JPlag sử dụng thuật toán Greedy-String- Tiling (GST) [30] được phát triển bởi Michael Wise, sau đó các tác giả đã cải tiến bằng cách kết hợp với thuật toán Karp-Rabin để nâng cao hiệu quả.
So sánh hai chuỗi Tokens A và B, mục đích là tìm ra chuỗi con chung lớn nhất của hai mã nguồn từ đó tính ra tỉ lệ trùng lắp so với tổng thể chương trình. Chuỗi con này phải đảm bảo nguyên tắc:
i. Một Token trong A chỉ được so khớp với một Token trong B
ii. Các chuỗi con được tìm thấy phải độc lập với vị trí của chúng trong chuỗi. Hay việc thay đổi vị trí các thành phần trong mã nguồn chương trình không làm ảnh hưởng tới việc so sánh.
Chương trình lỗi Chương trình mẫu 1 . . . . Chương trình mẫu n Chương trình mẫu 2 So sánh So sánh So sánh
22
1 GSTiling (String A, String B) {
2 tiles { } do
3 maxmatch MinimumMatchLength
4 matches { }
5 for unmarkerd tokens Aa in A do 6 for unmarkerd tokens Bb in B do 7 j 0
8 while (Aa+j == Bb+j ) ∩ unmarkedAa+j ∩ unmarked Bb+j do
9 j++
10 end
11 if (j == maxmatch) then
12 matches matches match (a; b; j)
13 elseif j> maxmatch then
14 matches match (a; b; j) }
15 maxmatch j
16 end 17 end
18 for match(a; b; maxmatch) ∈ matches do
19 for j = 0 … (maxmatch -1) do 20 mark (Aa+j);
21 mark (Bb+j); 22 end
23 Tiles tiles ∪ match (a; b; maxmatch)
24 end
25 while (maxmatch > MinimumMatchLengt);
26 return tiles;
Thuật toán 3.1. Thuật toán Greedy-String-Tiling (GST) [30]
Thuật toán GST (Thuật toán 3.1) có đầu vào là hai chuỗi Tokens A, B. Thuật toán trả về kết quả là vị trí bắt đầu đoạn con lớn nhất trong chuỗi A (a), chuỗi B (b) và độ dài chuỗi con lớn nhất của A và B (maxmatch) – dòng 23. Bắt đầu thuật toán, biến maxmatch được khởi tạo giá trị bằng giá trị MinimumMatchLength (MML) hay độ dài tối thiểu chuỗi con. Thuật toán thực hiện hai việc, đầu tiên là tìm kiếm chuỗi con tương đồng dài nhất, tiếp theo là với chuỗi con dài nhất được tìm thấy tiến hành đánh dấu vị trí để đảm bảo nguyên tắc không lặp lại (nguyên tắc i ở trên). Nếu maxmatch được tìm thấy lớn hơn giới hạn MML thì kết quả mới được tính là hợp lệ.
Tìm kiếm chuỗi con dài nhất mô tả từ dòng 5 đến dòng 17 gồm ba vòng lặp. Vòng for đầu tiên thực hiện duyệt tất cả các Token trong chuỗi A. Với mỗi Token trong chuỗi
23
A, lần lượt duyệt tất cả các Token trong chuỗi B (vòng for thứ hai) để so sánh. Nếu Token trong A và B trùng nhau thì thêm Token đó vào chuỗi Token con (vòng lặp while) và tăng giá trị độ dài chuỗi con maxmatch lên. Trong trường hợp chuỗi con được tìm thấy đã nằm trong danh sách đánh dấu thì giữ nguyên độ dài match và không thêm chuỗi con đó vào chuỗi con đang có (dòng 13 đến dòng 15).
Đánh dấu vị trí chuỗi con dài nhất với vòng for (từ dòng 18 đến 24), trả về kết quả giá trị match, bao gồm vị trí bắt đầu chuỗi con lớn nhất trong A và B và độ dài chuỗi con. Việc đánh dấu vị trí lỗi này thực hiện lặp lại cùng với quá trình tìm kiếm chuỗi con lớn nhất ở trên, để đảm bảo rằng một vị trí chuỗi con đã được đánh dấu là chuỗi con dài nhất match sẽ không được lặp lại ở một vị trí khác trong chương trình.
Giả sử cần so sánh hai mã nguồn, Mã nguồn 3.2 (đóng vai trò như mã nguồn A) và Mã nguồn 3.3 (đóng vai trò như mã nguồn B) trong thuật toán GST. Mã nguồn A là mã nguồn đúng được xem xét đưa ra làm gợi ý, mã nguồn B là mã nguồn lỗi đang cần gợi ý sửa. Mã nguồn B được so sánh với mọi mã nguồn đúng khác, khi so sánh với mỗi mã nguồn đúng cho một tỉ lệ tương đồng, dựa trên tỉ lệ này để lựa chọn gợi ý nào.
Code Tokens
1 import java.util.Scanner; IMPORT
2 public class Problem55184 { BEGINCLASS
3 public static int UCLN(int x, inty){ VARDEEF,VARDEF,BEGINMENTHOD
4 if (x==0) BEGINIF
5 return x; RETURN, ENDIF
6 while (x == y) { BEGINWHILE
7 if(x>y) x = x - y; BEGINIF, ASSIGN
8 else y = y - x; ELSE, ASSIGN, ENDIF
9 } ENDWHILE
10 System.out.println(y); APPLY
11 return x; RETURN
12 } ENDMENTHOD
13 public static void main(String arg[]) VARDEF, BEGINMENTHOD
14 {
15 Scanner nhap = new Scanner(System.in); VARDEF,NEWCLASS,ASSIGN
16 int a,b; VARDEF, VARDEF
17 a = nhap.nextInt(); ASSIGN
18 b = nhap.nextInt(); ASSIGN
19 System.out.println(UCLN(a,b)); APPLY
20 } ENDMENTHOD
21 } ENDCLASS
24
Áp dụng thuật toán GST, với giới hạn MML là 3, so sánh và tìm kiếm các chuỗi con trong mã nguồn A và B, kết quả được mô tả như trong Bảng 3.3. Ta tìm được được bốn chuỗi con trùng nhau của A và B. Các chuỗi con có độ dài tương ứng 14, 5, 3, 6, tổng độ dài các chuỗi con trùng nhau (coverage) trong A và B là 28. Tổng số Tokens trong mã nguồn A là 31 Tokens, mã nguồn B là 30 Tokens. Áp dụng công thức tính độ tương đồng [24] (Công thức 3.2) cho độ tương đồng giữa mã nguồn A và B là 91.8%. Với độ tương đồng của mã nguồn A và B là 91.8%, xếp theo thứ tự là độ tương đồng mức độ cao, mã nguồn A có thể là gợi ý sửa lỗi cho mã nguồn B
𝑆𝑖𝑚(𝐴, 𝐵) = 2 ∗ 𝑐𝑜𝑣𝑒𝑟𝑎𝑔𝑒(𝑡𝑖𝑡𝑙𝑒𝑠)
|𝐴| + |𝐵|
Bảng 3.3. So sánh và tìm chuỗi con trong mã nguồn A và B
IMPORT IMPORT BEGINCLASS BEGINCLASS VARDEF VARDEF VARDEF VARDEF BEGINMENTHOD BEGINMENTHOD BEGINIF BEGINIF RETURN RETURN ENDIF ENDIF BEGINWHILE BEGINWHILE BEGINIF BEGINIF ASSIGN ASSIGN ELSE ELSE ASSIGN ASSIGN ENDIF ENDIF ENDWWHILE ENDWWHILE RETURN APPLY ENDMENTHOD RETURN VARDEF ENDMENTHOD BEGINMENTHOD VARDEF VARDEF BEGINMENTHOD VARDEF VARDEF NEWCLASS NEWCLASS ASSIGN ASSIGN VARDEF VARDEF ASSIGN VARDEF VARDEF ASSIGN ASSIGN ASSIGN ASSIGN APPLY APPLY ENDMENTHOD ENDMENTHOD ENDCLASS ENDCLASS
Mã nguồn 3.2 (A) Mã nguồn 3.3 (B) (3.2)
25
Trong ví dụ nêu trên, ở Mã nguồn 3.3 cách đặt tên biến, tên hàm, cách viết biểu thức gán cũng khác so với Mã nguồn 3.2. Ngoài ra Mã nguồn 3.3 cũng được viết ngắn gọn, không chứa những dòng trống. Nhưng khi tách thành Tokens hai mã nguồn vẫn có tỉ lệ chuỗi con giống nhau nhiều, do đó tỉ lệ tương đồng cao. Tỉ lệ tương đồng càng cao thì chương trình đưa ra gợi ý càng hiệu quả. Như vậy kể cả chương trình có độ tương đồng đạt 100% thì cũng không có nghĩa là hai chương trình giống hệt nhau. Điều này giúp phát triển tư duy của học sinh trong quá trình học tập, bởi chương trình được gợi ý có nhiều điểm tương đồng chứ không giống hệt chương trình lỗi buộc học sinh phải tư duy, phải căn cứ chỉ lỗi mà sửa bài chứ không đơn giản là sao chép kết quả sang.
Trong thuật toán này cần lưu ý giá trị MML. Nếu ta lấy MML bằng một kết quả trả về có thể không chính xác, vì hai chuỗi có độ dài bằng một có khả năng trùng nhau