how to write good code

28
Code tốt có nghĩa là ??? Matsui Nobuyuki

Upload: minh-hoang

Post on 14-Jan-2017

967 views

Category:

Software


2 download

TRANSCRIPT

Page 1: How to write good code

Code tốt có nghĩa là ???Matsui Nobuyuki

Page 2: How to write good code

Code tốt có nghĩa là:• ( Đặt tại môi trường doanh nghiệp ) Code tốt có nghĩa

là 「 dễ đọc, dễ hiểu và dễ sửa chữa 」• Tuy nhiên không đồng nghĩa phải giúp giảm lượng truyền tải I/O

hay lượng sử dụng bộ nhớ • Cũng không phải giúp hệ thống hoạt động nhanh hơn

• Tuy nhiên với các phần mềm game hay hoạt động tại môi trường đặc thù thì đặc điểm trên cũng được tính vào để đánh giá code tốt

• Không phải code sử dụng các thủ pháp trick để giúp viết ngắn mà hệ thống vẫn hoạt động • Tại các cuộc thi programming, đặc điểm trên được tính là code tốt

Page 3: How to write good code

Tại sao viết được code “tốt” lại cần thiết ?• Tại doanh nghiệp, việc phát triển thường xuyên được làm

theo nhóm.• Cho dù là một mình phát triển đi chăng nữa, bản thân ở

hiện tại và sau 3 tháng là 2 trình độ hoàn toàn khác nhau.• Ở môi trường doanh nghiệp, code đại đa số là “được đọc”

hơn là “được viết"• Do ở môi trường doanh nghiệp rất hay có việc về mở rộng chức

năng, cho nên phải đọc tất cả những đoạn code “có vẻ" liên quan đến chỗ đó.

• Khi xảy ra lỗi, cần phải đọc thật nhanh code liên quan đến đoạn gây lỗi

• Cho nên điều quan trọng không phải là viết ra những đoạn code “có thể chạy được” mà là “dễ đọc và dễ hiểu"

Page 4: How to write good code

Phương châm cơ bản để viết được code tốt• Cách viết để dễ đọc

• Đơn giản hoá logic

• Cấu trúc lại code ( refactoring ) thường xuyên

• Viết để có thể test được

Page 5: How to write good code

Cách viết để dễ đọc • Sử dụng tên hợp lý

• Chọn từ rõ nghĩa thể hiện đúng điều mà variable hay function muốn làm• Những tên function như get hay tên variable kiểu result không thể hiện

được bên trong nó có gì• Hãy thêm vào những từ rõ nghĩa, cung cấp thông tin

• Ví dụ với biến chứa thông tin filesize => có thể đặt tên là uploaded_file_mb• Tránh dùng những từ chung chung như tmp hay buf

• Tuy nhiên với trường hợp tên biến chỉ bó hẹp trong phạm vi một màn hình thì vẫn có thể sử dụng không sao cả.

• Do phạm vi của các biến loop cũng bị giới hạn cho nên kiểu tên như “i” hay “j" cũng ok

• Với các trường hợp không thể chọn được tên rõ ràng và muốn đặt tên dài, cũng có nghĩa là chưa phân chia ra được các module hợp lý• Ví dụ nếu muốn đặt tên là canvas_max_px, tại sao không tách ra class

Canvas chứa biến instance tên là max_px?

Page 6: How to write good code

Cách viết để dễ đọc • Tuân theo cách đặt tên và coding thích hợp

• Hãy cố gắng tuân theo phong cách đã trở thành tiêu chuẩn đối với từng loại ngôn ngữ, hạn chế tạo ra các quy luật mới mình nghĩ ra• Ví dụ với Python:

• PEP8• http://legacy.python.org/dev/peps/pep-0008/

• Google Python Style Guide• http://google-styleguide.googlecode.com/svn/trunk/pyguide.html

• Indent và xuống dòng đều có ý nghĩa của nó cho nên cũng phải theo quy luật• Với Python, mặc dù indent không có liên quan trực tiếp tới control

structure, vẫn phải ý thức khi sử dụng

Page 7: How to write good code

Cách viết để dễ đọc • Chú trọng tính thống nhất cho vẻ ngoài

• Ví dụ • Trường hợp có nhiều function lấy chung một variable làm argument,

cho chung một thứ tự • Nếu định nghĩa variable mang ý nghĩa giống nhau tại nhiều module, cho

tên giống nhau

• Làm sao để người đọc không bị rơi tình trạng 「 Quái, cái hồi trước có phải thế này không nhỉ ?」

Page 8: How to write good code

Cách viết để dễ đọc • Hạn chế việc tự viết

• Sử dụng tối đa các chức năng thông dụng hay đã được đóng library sẵn với mỗi ngôn ngữ • Các xử lý tìm kiếm hay sắp xếp hầu hết đều được cung cấp code chạy

rất nhanh và an toàn từ các dòng xử lý ngôn ngữ• Những chức năng mà ai cũng muốn như xử lý chuỗi string.. hầu hết đều

được cung cấp sẵn. Với Java là apache commons, Ruby thì là active support

• Nên đọc qua không chỉ về specs ngôn ngữ mình cần sử dụng, mà cả về specs những library chính cũng như function được cung cấp.

Page 9: How to write good code

Cách viết để dễ đọc• Thêm comment thích hợp

• Không ghi những comment vô nghĩa kiểu như 「 đây là quy định của dự án 」

• Comment về tư tưởng thiết kế và ý đồ tại sao implement method và class đó• Cái này vốn được tạo ra để làm gì ?• Tại sao lại chọn logic này ? Có cách thức nào khác không ?

• Comment những chỗ mình cảm thấy mơ hồ• Ví dụ

• Với logic này cũng có thể chạy được tuy nhiên lượng cần tính toán với data sẽ là O(n^2)

• Do là destructive method 、 internal data sẽ bị thay đổi sau khi được gọi dù chỉ 1 lần

• Không comment những đoạn code nhìn cái là hiểu ý nghĩa• Những đoạn code phải comment về thứ tự xử lý để hiểu => bị coi là

code không tốt

Page 10: How to write good code

Đơn giản hoá logic • Phân nhánh điều kiện

• Đưa những điều kiện quan trọng và đơn giản lên trước

• Sử dụng luật De Morgan( https://vi.wikipedia.org/wiki/Lu%E1%BA%ADt_De_Morgan) hay Bìa Karnaugh( https://vi.wikipedia.org/wiki/B%C3%ACa_Karnaugh ) để đơn giản hoá điều kiện đi• Luật De Morgan

• not (A or B) == (not A) and (not B)• not (A and B) == (not A) or (not B)

Page 11: How to write good code

Đơn giản hoá Logic • Sử dụng hiệu quả Bìa Karnaugh ( by @kawasima )

• Ví dụ trường hợp có điều kiện như dưới đây

User profile (mục đang được lấy)

Số điện thoại mail address địa chỉ cách liên lạcmailmail

Điện thoại Mail VersionMail Version

Không có

Page 12: How to write good code

Đơn giản hoá Logic• Sử dụng hiệu quả Bìa Karnaugh ( by @kawasima )

• Nếu implement thông thường sẽ là:

Page 13: How to write good code

Đơn giản hoá logic • Sử dụng hiệu quả Bìa Karnaugh ( by @kawasima )

• Khi viết thử bằng Bìa Karnaugh sẽ được như sau:

Điện thoại: mail

Địa chỉ

Nếu viết bằng bìa Karnaugh sẽ rất dễ hiểu

Tóm lại Nếu mail address đã được đăng ký => email, nếu chưa được đăng ký nhưng địa chỉ đã được đăng ký => DM, cả 2 cùng chưa đăng ký => điện thoại

Page 14: How to write good code

Đơn giản hoá logic • Sử dụng hiệu quả Bìa Karnaugh ( by @kawasima )

• Có thể implement bằng điều kiện đơn giản hơn:

Page 15: How to write good code

Đơn giản hoá logic • Cố gắng cho method trả giá trị thật nhanh

• Thay vì phải nhớ mãi các điều kiện để phân định, khi xảy ra trường hợp bất thường => cố gắng đưa ra ngoài function hiện tại nhanh sẽ giúp code dễ hiểu hơn.

def construct_msg(num_pageview): msg = "" if (isValid(num_pageview)): foo() … bar() … msg = buz() else: msg = "invalid"

return msg

def construct_msg(num_pageview): if (not isValid(num_pageview)): return "invalid"

foo() … bar() … return buz()

Page 16: How to write good code

Đơn giản hoá logic • Không viết những đoạn code trick

• Dù specs của ngôn ngữ đang dùng có support đi chăng nữa, không viết những kiểu code trick Không viết kiểu để tự mãn với bản thânxx = 1yy = 2def f(x,y): return x + y + xx + yy

globals().update({"xx":1,"yy":2,"f":lambda x,y:x+y+xx+yy})

Page 17: How to write good code

Đơn giản hoá logic •Không viết những đoạn code trick

•Dù có nói là được phép với specs của ngôn ngữ hiện tại đi chăng nữa, không viết những kiểu code trick Không viết kiểu để tự mãn với bản thân

def check(x): if x%2 == 0: return even()     else: return odd()

def check(x): return odd() if x%2 else even()

def check(x): return (x%2 and [odd()] or [even()])[0]

=def check(x): return [even, odd][x%2]()

Page 18: How to write good code

Đơn giản hoá logic • Những phần implement có tính chất giúp giảm thiểu bộ nhớ và nâng cao tính

năng để đến sau cùng• Trong nguyên lý Pareto( https://vi.wikipedia.org/wiki/Nguy%C3%AAn_l%C3%BD_Pareto ) đã có

ghi: xét trên tổng thể, chỉ có 1 phần nhỏ các đoạn code gây ra việc tốn bộ nhớ và làm chức năng kém đi

• Đầu tiên hãy chú ý tới viết code sao cho dễ hiểu• Kiểm duyện lại tính năng trên toàn bộ hệ thống, sau đó lọc ra các điểm chính gây ảnh

hưởng tới bộ nhớ và làm giảm tính năng để tập trung sửa chữa• Những chỗ cần thiết tính năng phải thật tốt, có thể xem xét viết 1 vài chỗ bằng

C cũng rất tốt• Tuning cho SQL là cách hiệu quả nhất để nâng cao tính năng

• Tuy nhiên với những nest loop hay algorithm tìm kiếm O(n2).. không có sẵn thì cần xem xét kỹ ngay từ đầu để tránh những đoạn code hiệu suất thấp

※Nguyên lý Pareto 「 Hầu hết các giá trị đều chỉ được sinh ra bởi 1 số thành phần cấu trúc nên toàn thể mà thôi 」 「 Nguyên lý 8:2 」

Page 19: How to write good code

Tái cấu trúc code ( Refactoring )• Những xử lý không tóm gọn trong một màn hình cần suy

nghĩ xem có thể phân chia module được không• Xử lý không liên quan trực tiếp tới việc đoạn code muốn thực

hiện ( mục đích có thể tóm trong 1 dòng comment), cần cắt ra module khác

• Nếu đặt tên một cách hợp lý, thậm chí không cần nhìn code của module được cắt ra vẫn có thể hiểu được đại khái nội dung xử lý của module đó => code cũ đã trở nên dễ hiểu hơn

• Vậy phải phân chia bằng cách nào ?

Page 20: How to write good code

Tái cấu trúc code ( Refactoring• the Open-Closed Principle ( OCP ) (định luật đóng-mở)

• 「 Các element cấu trúc của software cần được mở với support mở rộng chức năng, nhưng đóng với việc sửa chữa 」

• Những module được ý thức theo luật OCP sẽ không làm ảnh hưởng tới các module khác khi bị sửa chữa= Nguyên lý của thiết kế hướng đối tượng

• Tầm quan trọng của specs API• Nếu specs API công bố ra bên ngoài ( ứng với input của method sẽ

cho output là gì, hay những “tác dụng phụ” xảy ra là gì ) không thay đổi => Code phía trong bị thay đổi đi chăng nữa cũng không ai quan tâm

Page 21: How to write good code

Tái cấu trúc code ( Refactoring)• Tác dụng phụ của function

• ( ý nghĩa mặt toán học ) function = thứ chuyển từ input sang ouput

• Những thứ nằm ngoài việc chuyển input sang ouput, làm ảnh hưởng đến môi trường ngoài function= Tác dụng phụ• Input/Output của network và file, input/output của màn hình, input/output

của database.. đều là tác dụng phụ• input/output tới các variable ở phía ngoài function cũng là tác dụng phụ

• Function không có tác dụng phụ thì dù được gọi trong bất cứ trường hợp nào cũng đều cho một kết quả• Trở thành module dễ test và stable

• Ngôn ngữ functional programming về cơ bản tạo ra hệ thống dựa trên các function không có tác dụng phụ => bằng việc hạn chế tối đa những tác dụng phụ để có một hệ thống vững chắc.• Tuy nhiên, một hệ thống hoàn toàn không có tác dụng phụ cũng không

có ý nghĩa đặc biệt gì cả

Page 22: How to write good code

Tái cấu trúc code ( Refactoring)• Tận dụng ưu điểm của functional programming

• Scope của biến càng nhỏ càng tốt• Tránh dùng global variable

• Chỉ set giá trị 1 lần ( không sử dụng lại biến giống nhau và viết lại )• Biến được giới hạn trong function nhỏ (ví dụ là biến loop) có thể viết lại cũng không sao.• Nếu tạo ra function mà ngầm viết lại những biến phụ thuộc sẽ dẫn tới khả năng sinh bug rất

khó chữa ở những nơi mình không ngờ tới• Cũng cần phải chú ý tới việc viết lại nội dung biến của function như list.append()

• Ví dụ: Nếu có ký hiệu list intension, list cũ sẽ vẫn như thế và list mới sẽ được tạo

>>> x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>>> y = [i * 2 for i in x if i%2 == 0]>>> y[4, 8, 12, 16, 20]>>> x[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Page 23: How to write good code

Tái cấu trúc code ( Refactoring)•Tận dụng ưu điểm của functional programming

• Không viết lại argument gửi tới function trong function đó • Argument của function trong Java và Python được đưa để tham chiếu• => Về mặt specs, có thể viết lại được nội dung argument object tuy

nhiên về cơ bản không được làm.• Output trả về từ function tất cả phải dưới dạng return value

• Khi xảy ra exception, không phải return value mà phải raise exception

• Rút cuộc ・・・• Việc phân chia rõ ràng 「 phần được coi như là tác dụng

phụ(external input/output.. ) 」 , 「 phần thực hiện xử lý phụ thuộc vào internal data của class 」 và 「 function không có tác dụng phụ 」 , rồi sau đó localize phạm vi mà code đó ảnh hưởng là rất quan trọng.

Page 24: How to write good code

Tái cấu trúc code ( Refactoring)• the Single Responsibility Principle ( SRP ) (định luật

nhiệm vụ đơn)• Nhiệm vụ của class chỉ có một

• Bắt buộc phải viết được nhiệm vụ của class chỉ bằng 1 dòng comment dạng 「 Class này thực hiện nhiệm vụ “abc" 」

• Lý do thay đổi implement của class chỉ có thể vì cần sửa chữa hay mở rộng nhiệm vụ “abc" đó• Khi sửa chữa program bằng 1 lý do ngoài việc 「 sửa chữa cho nhiệm

vụ “abc" ấy 」 mà code trong class nhiệm vụ “abc" phải bị thay đổi• => việc phân chia class đã bị nhầm lẫn

Page 25: How to write good code

Tái cấu trúc code ( Refactoring)• Để phân biệt class hợp lý cần sử dụng design pattern

• Tổng hợp best practice để phân chia class thật chuẩn • Truyền thống nhất : GoF ( Gang of Four ) design pattern

( https://en.wikipedia.org/wiki/Design_Patterns )• Ngoài GoF, có rất nhiều loại design pattern đã được đề ra.

• Tuy nhiên, nếu sử dụng pattern chỉ vì có cảm giác “muốn sử dụng pattern" thì sẽ lợi bất cập hại

Page 26: How to write good code

Tái cấu trúc code ( Refactoring)• Khi đánh giá độ hợp lý của code, cần sử dụng metrics

• Độ liên kết và độ kết hợp• Tiêu chuẩn kiểm tra OCP và SRP của module • Hoàn hảo nhất là độ liên kết cao và độ kết hợp thấp• Có thể đưa ra con số cụ thể bằng code metric tool

• Những con số cụ thể không có ý nghĩa lắm, điều quan trọng là dùng nó để nhìn ra thiên hướng chung

• Độ phức tạp tuần hoàn của McCabe• Tiêu chuẩn kiểm tra độ phức tạp của code ( mức độ của loop và phân

nhánh )• Thông thường dưới 10 được coi là tốt

• Quá 30 sẽ bị coi là module có cấu trúc thất bại, quá 50 sẽ là không thể test, quá 75 thì dù có sửa ít thế nào vẫn có thể sinh bug

Page 27: How to write good code

Viết để có thể test được • Những function không có tác dụng phụ sẽ có thể test dễ

dàng• Càng đơn giản, càng bao phủ nhiều càng tốt• Tuy nhiên không phải test mù quáng bằng rất nhiều giá trị, hãy suy

nghĩ lý do rõ ràng tại sao phải test bằng input đó• Giá trị giới hạn

• Max và min của giá trị mong đợi cộng hoặc trừ với 1..• Giá trị đặc biệt

• Với trường hợp số thì là 0• Với trường hợp chữ ( utf-8 ) , bit pattern mà có chữ là 1 byte ( US-

ASCII ) , chữ là 2 byte (Greek alphabet), chữ là 3 byte ( kanji thông thường và số tròn.. ) , chữ có trên 4 byte ( kanji tiêu chuẩn tầng 3, 4 của JIS X 0213 ) ..

• Với trường hợp chữ ( Shift-JIS và CP932 ) , từ 「表」 và 「ー」 .. có byte thứ 2 là character 0x5c ( backslash ) và character bị hỏng do chuyển từ CP932 sang UTF-8 (~) ..

Page 28: How to write good code

Viết để có thể test được • Xử lý có tác dụng phụ

• Sử dụng stub và driver để viết test• Sử dụng hợp lý chức năng được test framework cung cấp

• Bằng việc test liên tục, có tính tiếp nối với program đã được phân chia module hợp lý và ý thức để test được

• => dù có develop theo nhóm đi chăng nữa vẫn có thể yên tâm và mở rộng chức năng.