1. Trang chủ
  2. » Luận Văn - Báo Cáo

Ứng dụng kỹ thuật tải cấu trúc mã nguồn để triển khai dò tìm và cải tiến các đoạn mã xấu trong chương trình c

20 191 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 470,23 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

LỜI CAM ĐOAN Tôi xin cam đoan nội dung luận văn "Ứng dụng kỹ thuật tái cấu trúc mã nguồn để triển khai dò tìm và cải tiến các đọan mã xấu trong chương trình C# ", dưới sự hướng dẫn của T

Trang 1

BỘ GIÁO DỤC VÀ ĐÀO TẠO

ĐẠI HỌC ĐÀ NẴNG

    

BÁO CÁO

LUẬN VĂN THẠC SĨ KỸ THUẬT

NGÀNH KHOA HỌC MÁY TÍNH

TÊN ĐỀ TÀI:

ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ

TRIỂN KHAI DÒ TÌM VÀ CẢI TIẾN CÁC ĐOẠN

MÃ XẤU TRONG CHƯƠNG TRÌNH C#

Họ tên CBHD : TS.NGUYỄN THANH BÌNH

ĐÀ NẴNG, 11/2008

Trang 2

LỜI CAM ĐOAN

Tôi xin cam đoan nội dung luận văn "Ứng dụng kỹ thuật tái cấu trúc mã nguồn để triển khai dò tìm và cải tiến các đọan mã xấu trong chương trình C# ", dưới sự hướng dẫn của TS Nguyễn Thanh Bình, là công trình do tôi trực tiếp nghiên cứu

Tôi xin cam đoan các số liệu, kết quả nghiên cứu trong luận văn là trung thực và chưa từng được công bố trong bất cứ công trình nào trước đây

Tác giả

Nhiêu Lập Hòa

Trang 3

MỤC LỤC

LỜI CAM ĐOAN 2

MỤC LỤC 3

DANH MỤC HÌNH ẢNH 5

MỞ ĐẦU 6

CHƯƠNG I: KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN (REFACTORING) 7

I.1 ĐỊNH NGHĨA KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN 7

I.1.1 Ví dụ minh họa 7

I.1.2 Định nghĩa kỹ thuật tái cấu trúc mã nguồn 19

I.2 HIỆU QUẢ CỦA TÁI CẤU TRÚC MÃ NGUỒN 20

I.2.1 Refactoring cải thiện thiết kế phần mềm 20

I.2.2 Refactoring làm mã nguồn phần mềm dễ hiểu 20

I.2.3 Refactoring giúp phát hiện và hạn chế lỗi 21

I.2.4 Refactoring giúp đấy nhanh quá trình phát triển phần mềm 21

I.3 KHI NÀO THỰC HIỆN TÁI CẤU TRÚC MÃ NGUỒN 22

I.3.1 Refactor khi thêm chức năng 22

I.3.2 Refactor khi cần sửa lỗi 22

I.3.3 Refactor khi thực hiện duyệt chương trình 23

I.4 CÁC KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN 23

I.4.1 Danh mục các kỹ thuật tái cấu trúc mã nguồn 23

I.5 NHẬN XÉT VÀ KẾT LUẬN 26

CHƯƠNG II: LỖI CẤU TRÚC (BAD SMELLS) TRONG MÃ NGUỒN 27

II.1 KHÁI NIỆM VỀ LỖI CẤU TRÚC (BAD SMELLS) 27

II.2 LỖI CẤU TRÚC VÀ GIẢI PHÁP CẢI TIẾN 27

II.2.1 Duplicated Code - Trùng lặp mã 27

II.2.2 Long Method – Phương thức phức tạp 28

II.2.3 Large Class – Qui mô lớp lớn 30

II.2.4 Long Parameter List - Danh sách tham số quá dài 31

II.2.5 Divergent Change – Cấu trúc lớp ít có tính khả biến 32

II.2.6 Shotgun Surgery – Lớp được thiết kế không hợp lý và bị phân rã 32

II.2.7 Feature Envy – Phân bố phương thức giữa các lớp không hợp lý 33

II.2.8 Data Clumps – Gôm cụm dữ liệu 34

II.2.9 Primitive Obsession – Khả năng thể hiện dữ liệu của lớp bị hạn chế 34

II.2.10 Switch Statements – Khối lệnh điều kiện rẽ hướng không hợp lý 36

II.2.11 Lazy Class – Lớp được định nghĩa không cần thiết 38

II.2.12 Speculative Generality – Cấu trúc bị thiết kế dư thừa 38

II.2.13 Temporary Field – Lạm dụng thuộc tính tạm thời 39

II.2.14 Message Chains –Chuỗi phương thức liên hoàn khó kiểm soát 39

II.2.15 Middle Man – Quan hệ ủy quyền không hợp lý/logic 39

II.2.16 Inapproprite Intimacy - Cấu trúc thành phần riêng không hợp lý 41

II.2.17 Alternative Classes with Different Interfaces - Đặc tả lớp không rõ ràng 41 II.2.18 Incomplete Library Class – Sử dụng thư viện lớp chưa được hòan chỉnh 41 II.2.19 Data Class – Lớp dữ liệu độc lập 42

Trang 4

II.2.21 Comments – Chú thích không cần thiết 43

II.3 NHẬN XÉT VÀ KẾT LUẬN 44

CHƯƠNG III: NỀN TẢNG NET VÀ NGÔN NGỮ LẬP TRÌNH C# 45

III.1 TỔNG QUAN VỀ NỀN TẢNG NET 45

III.1.1 Định nghĩa NET 45

III.1.2 Mục tiêu của NET 45

III.1.3 Dịch vụ của NET 45

III.1.4 Kiến trúc của NET 46

III.2 NGÔN NGỮ LẬP TRÌNH C# 47

III.2.1 Tổng quan về ngôn ngữ lập trình C# 47

III.2.2 Đặc trưng của các ngôn ngữ lập trình C# 47

III.3 MÔI TRƯỜNG PHÁT TRIỂN ỨNG DỤNG VISUAL STUDIO NET 48

CHƯƠNG IV: ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ DÒ TÌM VÀ CẢI TIẾN CÁC ĐOẠN MÃ XẤU TRONG CHƯƠNG TRÌNH C# 49

IV.1 GIẢI PHÁP VÀ CÔNG CỤ HỖ TRỢ REFACTOR 49

IV.1.1 Đặc tả giải pháp triển khai 49

IV.1.2 Một số công cụ và tiện ích hỗ trợ việc dò tìm và cải tiến mã xấu 50

IV.1.3 Thử nghiệm minh họa các công cụ hỗ trợ refactor trong VS.Net 57

IV.1.4 Nhận xét và đánh giá 80

IV.2 ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ DÒ TÌM VÀ CẢI TIẾN CÁC ĐOẠN MÃ XẤU TRONG CHƯƠNG TRÌNH C# 81

IV.2.1 Thực hiện kỹ thuật tái cấu trúc mã nguồn trên chương trình thực tế 82

IV.2.2 Phân tích và đánh giá kết quả thực hiện 94

IV.3 NHẬN XÉT VÀ KẾT LUẬN 95

CHƯƠNG V: KẾT LUẬN 96

V.1 ĐÁNH GIÁ KẾT QUẢ CỦA ĐỀ TÀI 96

V.2 PHẠM VI ỨNG DỤNG 96

V.3 HƯỚNG PHÁT TRIỂN 97

V.3.1 Triển khai áp dụng trên các ngôn ngữ khác 97

V.3.2 Thử nghiệm xây dựng một refactoring tool tích hợp vào VS.NET 97

TÀI LIỆU THAM KHẢO 98

Trang 5

DANH MỤC HÌNH ẢNH

Trang 6

MỞ ĐẦU

Trong qui trình phát triển phần mềm hiện nay, một thực tế đang tồn tại ở các công

ty sản xuất phần mềm là các lập trình viên thường xem nhẹ việc tinh chỉnh mã nguồn và kiểm thử Ngoài lý do đơn giản vì đó là một công việc nhàm chán, khó được chấp nhận đối với việc quản lý vì sự tốn kém và mất thời gian, còn một nguyên nhân khác là chúng

ta không có những phương pháp và tiện ích tốt hỗ trợ cho những việc này Điều này dẫn đến việc phần lớn các phần mềm không được kiểm thử đầy đủ và phát hành với các nguy

cơ lỗi tiềm ẩn

Phương thức phát triển phần mềm linh hoạt[15] bắt đầu xuất hiện vào đầu những năm 90 với mục tiêu là phần mềm phải có khả năng biến đổi, phát triển và tiến hóa theo thời gian mà không cần phải làm lại từ đầu Phương thức này được thực hiện dựa trên hai

kỹ thuật chính là tái cấu trúc mã nguồn (refactoring) và kiểm thử (developer testing) Vì

thế việc nghiên cứu và ứng dụng kỹ thuật tái cấu trúc mã nguồn nhằm tối ưu hóa mã nguồn và nâng cao hiệu quả kiểm thử là một nhu cầu cần thiết trong quá trình thực hiện

và phát triển phần mềm

Đề tài “Ứng dụng kỹ thuật tái cấu trúc mã nguồn để triển khai dò tìm và cải

tiến các đoạn mã xấu trong chương trình C#” được thực hiện với mục đích nghiên cứu

cơ sở lý thuyết kỹ thuật tái cấu trúc mã nguồn và áp dụng để triển khai việc dò tìm và cải tiến mã xấu (lỗi cấu trúc) trong các chương trình hiện đại và phổ biến hiện nay (C#) Toàn bộ nội dung của luận văn bao gồm các chương:

Chương 1: Kỹ thuật tái cấu trúc mã nguồn (refectoring)

trong các chương trình C#

Trang 7

CHƯƠNG I: KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN (REFACTORING)

I.1 ĐỊNH NGHĨA KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN

I.1.1 Ví dụ minh họa

Phương thức tiếp cận và tìm hiểu hiệu quả nhất với một khái niệm hay một kỹ thuật mới trong tin học là thông qua các ví dụ minh họa [11] Với ví dụ dưới đây, chúng

ta sẽ hiểu refactoring là gì cũng như cách thực hiện và hiệu quả của nó trong qui trình công nghệ phát triển phần mềm

Bài toán ví dụ: Chương trình trả lại kết quả danh sách các số nguyên tố

(Bài toán này sử dụng thuật toán Eratosthenes)

Nội dung thuật toán Eratosthenes:

- Viết một danh sách các số từ 2 tới maxNumbers mà ta cần tìm Gọi là list A

- Viết số 2, số nguyên tố đầu tiên, vào một list kết quả Gọi là list B

- Xóa bỏ 2 và bội của 2 khỏi list A

- Số đầu tiên còn lại trong list A là số nguyên tố Viết số này sang list B

- Xóa bỏ số đó và tất cả bội của nó khỏi list A

- Lặp lại các bước 4 and 5 cho tới khi không còn số nào trong list A

Chương trình khởi đầu:

public class PrimeNumbersGetter {

private int maxNumber;

public PrimeNumbersGetter(int maxNumber){

this.maxNumber = maxNumber;

}

public int[] GetPrimeNumbers() { // Use Eratosthenes's sieve

umbers[i] = true;

}

int j = 2;

while (j <= (int)Math.Sqrt(maxNumber) + 1) {

for (int k = j + j; k <= maxNumber; k += j){

}

j++;

while (!numbers[j]) {

j++;

if (j > maxNumber) break;

}

}

Trang 8

List<int> l = new List<int>();

for (int k = 2; k <= maxNumber; ++k) {

if (numbers[k]) l.Add(k);

}

return l.ToArray();

}

Trước khi refactoring, chúng ta cần viết kiểm thử cho phần mã nguồn đó Phần kiểm thử này là yếu tố cần thiết bởi vì quá trình refactoring có thể phát sinh lỗi Mỗi khi chúng ta thực hiện một lần refactoring, chúng ta nên thực hiện kiểm thử lại chương trình một lần, để đảm bảo chương trình không bị lỗi Kiểm thử tốt làm giảm thời gian cần thiết

để tìm lỗi Chúng ta nên thực hiện xen kẽ việc kiểm thử và refactoring để mỗi khi có lỗi phát sinh, thì cũng không quá khó để tìm ra lỗi đó

Trong ví dụ này, chúng có thể thêm đoạn mã kiểm thử như sau:

public class Program {

public static void Main(string[] args) {

if (!IsEqualNumbers(new PrimeNumbersGetter(4).GetPrimeNumbers(),

new int[] { 2, 3 }))

return;

if (!IsEqualNumbers(new PrimeNumbersGetter(5).GetPrimeNumbers(),

new int[] { 2, 3, 5 }))

if (!IsEqualNumbers(new PrimeNumbersGetter(6).GetPrimeNumbers(),

new int[] { 2, 3, 5 }))

if (!IsEqualNumbers(new PrimeNumbersGetter(100).GetPrimeNumbers(),

new int[] { 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 }))

Console.WriteLine("Success!");

}

private static bool IsEqualNumbers(int[] numbers1, int[] numbers2){

if (numbers1.Length != numbers2.Length) return false;

for (int i = 0; i < numbers1.Length; ++i) {

if (numbers1[i] != numbers2[i]) return false;

}

}

return true;

}

Ta nhận thấy rằng phương thức này quá dài, và nó xử lý rất nhiều công việc khác nhau Trong trường hợp này, nên sử dụng kĩ thuật “Extract Method” trong các kĩ thuật refactoring nhằm tạo ra các phương thức nhỏ hơn, dễ đọc và dễ bảo trì khi có yêu cầu thay đổi chương trình

Với đoạn mã nguồn khởi tạo list các số ban đầu ( list A ):

Trang 9

bool[] numbers = new bool[maxNumber + 1];

for (int i = 0; i < numbers.Length; ++i){

numbers[i] = true;

}

Ta nên trích xuất nó thành một phương thức khác, sử dụng “Extract Method”

public int[] GetPrimeNumbers() {

bool[] numbers = InitialNumbers();

// Other codes

}

private bool[] InitialNumbers(){

bool[] numbers = new bool[maxNumber + 1];

for (int i = 0; i < numbers.Length; ++i){

numbers[i] = true;

}

return numbers;

}

Sau khi thực hiện việc refactoring như trên, chúng ta nên nhớ rằng phải thực hiện chạy lại chương trình kiểm thử để đảm báo rằng việc refactoring không làm thay đổi tính đúng đắn của chương trình

Tương tự với đoạn mã nguồn thực hiện xuất ra danh sách kết quả chứa các số nguyên tố (list B)

List<int> l = new List<int>();

for (int k = 2; k <= maxNumber; ++k) {

if (numbers[k]) l.Add(k);

}

return l.ToArray();

Ta cũng tách nó ra thành một phương thức khác

public int[] GetPrimeNumbers() {

// Other codes

return GetPrimeNumbersArray(numbers);

}

private int[] GetPrimeNumbersArray(bool[] numbers) {

List<int> l = new List<int>();

for (int k = 2; k <= maxNumber; ++k) {

if (numbers[k]) l.Add(k);

}

return l.ToArray();

}

Trang 10

Bây giờ chúng ta sẽ tinh chỉnh ở phần mã nguồn còn lại, đó là vòng lặp while

int j = 2;

while (j <= (int)Math.Sqrt(maxNumber) + 1){

for (int k = j + j; k <= maxNumber; k += j){

numbers[k] = false;

}

j++;

while (!numbers[j]){

j++;

if (j > maxNumber) break;

}

}

Với đoạn mã nguồn trên, câu lệnh if là không cần thiết, ta có thể bỏ đi, đƣa điều kiện lên

vòng while nhƣ sau:

int j = 2;

while (j <= (int)Math.Sqrt(maxNumber) + 1) {

for (int k = j + j; k <= maxNumber; k += j) {

numbers[k] = false;

}

j++;

while (!numbers[j] && j < maxNumber) {

j++;

}

}

Ta thấy rằng, câu lệnh điều khiển trong vòng for không “đẹp” và khó đọc, ta nên sửa đổi tên biến k thành i

int j = 2;

while (j <= (int)Math.Sqrt(maxNumber) + 1) {

for (int i = 2; i * j <= maxNumber; i++){

numbers[i * j] = false;

}

j++;

while (!numbers[j] && j < maxNumber) {

j++;

}

}

Với vòng while ở trên, mục đích chỉ là duyệt danh sách các phần tử trong list A, nên ta có thể chuyển sang sử dụng vòng lặp for nhƣ sau:

for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {

if (!numbers[j]) continue;

for (int i = 2; i * j <= maxNumber; i++) {

numbers[i * j] = false;

}

}

Trang 11

Với đoạn mã nguồn

for (int i = 2; i * j <= maxNumber; i++) {

numbers[i * j] = false;

}

Thực hiện việc xóa bỏ các bội số của các số nguyên tố Do đó, có thể tách chúng ra thành một phương thức Kết quả thu được là:

public int[] GetPrimeNumbers() {

bool[] numbers = InitialNumbers();

for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {

if (!numbers[j]) continue;

RemoveMultiple(numbers, j);

}

}

private void RemoveMultiple(bool[] numbers, int j) {

for (int i = 2; i * j <= maxNumber; i++) {

numbers[i * j] = false;

}

}

Tiếp tục với đoạn mã nguồn

private void RemoveMultiple(bool[] numbers, int j) {

for (int i = 2; i * j <= maxNumber; i++) {

numbers[i * j] = false;

}

}

Ta nên đặt tên biến, tham số truyền vào sao cho mã nguồn trở nên dễ đọc nhất

private void RemoveMultiple(bool[] numbers, int number) {

for (int i = 2; i * number <= maxNumber; i++) {

}

}

Đến đây ta thấy rằng phương thức GetPrimenumbers() đã trở nên ngắn gọn, dễ đọc hơn nhất nhiều Tuy nhiên, chúng ta cũng cần nghĩ rằng chương trình này đã thực sự đẹp chưa và có cần refactor nữa hay không?

Ta nhận thấy rằng biến numbers được sử dụng là tham số để truyền vào một số phương thức Do đó, ta nên chuyển khai báo biến numbers thành biến thành viên của lớp Khi đó, các phương thức sẽ sử dụng trực tiếp biến thành viên này, chứ không phải sử dụng tham số truyền vào Khi chuyển biến numbers thành biến thành viên, thì phương thức InitialNumbers() không cần nữa, mà ta sẽ chuyển khởi tạo biến này trong constructor của lớp Khi đó chúng ta cần phải xóa hết các tham số truyền vào trong các phương thức sử dụng biến numbers

Trang 12

Khi đó lớp PrimeNumbersGetter

public class PrimeNumbersGetter {

private int maxNumber;

public PrimeNumbersGetter(int maxNumber) {

this.maxNumber = maxNumber;

}

public int[] GetPrimeNumbers() {

bool[] numbers = InitialNumbers(); // Other codes

}

// Other codes

private bool[] InitialNumbers() {

bool[] numbers = new bool[maxNumber + 1];

numbers[i] = true;

}

return numbers;

}

}

Sẽ được chỉnh sửa thành

public class PrimeNumbersGetter {

private int maxNumber;

private bool[] numbers;

public PrimeNumbersGetter(int maxNumber) {

this.maxNumber = maxNumber;

numbers[i] = true;

}

}

public int[] GetPrimeNumbers() {

// Other codes

}

// Other codes, Method InitialNumbers() is nomore available

}

Việc sử dụng biến numbers theo kiểu bool[] đã được định nghĩa sẵn trong Thư viện System.Collections, đó là kiểu BitArray Do đó, ta nên chuyển khai báo của biến numbers thành kiểu BitArray Như vậy, ta sẽ loại bỏ được việc khởi tạo giá trị cho biến mảng numbers, bởi BitArray đã thực hiện điều đó Chúng ta cần lưu ý rằng biến mảng numbers lúc này chỉ đánh số từ 2 cho đến maxNumber, do đó, nó chỉ có (maxNumber - 1) phần tử, và nếu số duyệt là j thì vị trí của nó là numbers[j - 2] Sau khi sửa, ta có đoạn

mã nguồn sau:

Ngày đăng: 31/08/2016, 15:44

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w