Tổng quan về giải pháp gợi ý chương trình

Một phần của tài liệu Nghiên cứu giải pháp và công cụ hỗ trợ gợi ý sửa lỗi cho các chương trình java (Trang 29)

Chương 3 Phương pháp xác định vị trí lỗi và gợi ý sửa lỗi

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 một cách ngẫu nhiên là rất cao, chúng có thể ở các vị trí khác nhau trong chương trình, và việc tìm chuỗi con dài nhất không khả thi. Trong bài toán này các chương trình của học sinh phần đa là các chương trình đơn giản, số lượng dòng lệnh không nhiều. Nếu lựa chọn giá trị MML quá cao thì khả năng không tìm được kết quả tương xứng hoặc độ phù hợp rất thấp. Nếu lựa chọn MML quá thấp, giả sử bằng một như mô tả bên trên sẽ không hiệu quả. Do đó trong quá trình cài đặt thuật toán, giá trị MML được đặt là ba, là giá trị phù hợp.

Để làm đầy thêm bộ sưu tập và tăng hiệu quả gợi ý do những chương trình học sinh viết sẽ gần gũi hơn với nhau, nên sau khi chương trình của học sinh được sửa và chạy chính xác, chương trình sẽ được gắn nhãn và đưa vào bộ sưu tập tại mục tương ứng với đề bài.

3.4. Ví dụ minh họa

Để minh họa cho phương pháp định vị lỗi và gợi ý sửa lỗi được mô tả trong Chương 3, học viên đã áp dụng phương pháp vào bài toán số nguyên tố và phân tích các kết quả thu được.

Tên bài toán: in ra dãy các số nguyên tố nhỏ hơn hoặc bằng N

Dữ liệu cho bài toán gồm: bốn code lỗi Problem1.java, Problem2.java, Problem3.java, Problem4.java; một code đúng Correct.java do giáo viên dây dựng và một số code đúng khác do sinh viên viết được lấy từ cơ sở dữ liệu của hệ thống OASIS; bộ các ca kiểm thử trong tệp SolutionTest.java. Sử dụng chung một bộ các ca kiểm thử như mô tả trong Bảng 4.1 cho tất cả các code lỗi. Theo đó ca kiểm thử nào được xác định là NT hay PT phụ thuộc vào code.

26

Bảng 4.1. Danh sách các ca kiểm thử cho bài toán số nguyên tố

Ca kiểm thử N Expected Output Test1 1 "" Test2 2 "2" Test3 3 "2 3" Test4 4 "2 3" Test5 5 "2 3 5" Test6 235 "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233"

Bảng 4.1 mô tả danh sách các ca kiểm thử cho bài toán số nguyên tố. Tương ứng với độ phủ C3, ta xây dựng bộ các ca kiểm thử gồm sáu ca kiểm thử được đánh dấu (Test1, Test2, Test3, Test4, Test5, Test6). Cột thứ hai là các giá trị N cần kiểm tra. Cột ba là các giá trị ra mong muốn theo mô tả bài toán. Trường hợp N bằng 1 chuỗi in ra là chuỗi trống (không có số nguyên tố nào nhỏ hơn hoặc bằng 1). Các giá trị khác tương ứng được mô tả chi tiết trong bảng. Quá trình định vị lỗi cho từng code bằng Gzoltar cho kết quả như các Bảng 4.2, Bảng 4.3, Bảng 4.4, Bảng 4.5.

Bảng 4.2. Kết quả định vị lỗi Problem1

Code Điểm nghi ngờ

1 package soNguyenTo.Problem1; 0

2 public class Problem1 { 0

3 public static boolean a(int n) { 0

4 if(n==2 || n==3) return true; 0.577

5 if(n<2) return false; 0.577

6 for(int i=4; i< n+1; i++) { 0.816

7 if(n%i==0) return false; 0.816

8 } 0

9 return true; 0

10 } 0

11 public static String primes(int n) { 0

12 String s = ""; 0.577

13 for( int i=0; i<=n; i++) { 0.577

14 if(a(i)) 0.577

15 s+=i+" "; 0.632

16 } 0

17 return s; 0.577

27

Trong kết quả định vị lỗi bài Problem2 tại Bảng 4.2, các câu lệnh có điểm nghi ngờ bằng 0 (câu lệnh 1, 2, 3, 8, 9, 10, 11, 16, 18) được hiểu là không có lỗi. Khả năng gây lỗi của các câu lệnh còn lại được đánh giá bằng các giá trị lớn hơn 0, trong đó câu lệnh 6 và 7 có điểm nghi ngờ cao nhất (0.816). Kết quả này chỉ lỗi trên nhiều câu lệnh,

Một phần của tài liệu Nghiên cứu giải pháp và công cụ hỗ trợ gợi ý sửa lỗi cho các chương trình java (Trang 29)