07/09/2018, 18:01

Code Thối (Code Smells) là gì và một vài tip để cho code của bạn thơm tho hơn

Code thối là cái gì vậy ? Code Smells (Mã xấu, hay "Code Thối nếu thích") là từ được dùng để chỉ phần code mà ta cảm thấy không ổn. Đây thường là đoạn code vi phạm những quy tắc trong lập trình. Giả sử bạn đang đọc một bài viết và bắt gặp một lỗi chính tả. Ngay lập tức, bạn có cảm giác ngờ ...

Code thối là cái gì vậy ?

Code Smells (Mã xấu, hay "Code Thối nếu thích") là từ được dùng để chỉ phần code mà ta cảm thấy không ổn. Đây thường là đoạn code vi phạm những quy tắc trong lập trình. Giả sử bạn đang đọc một bài viết và bắt gặp một lỗi chính tả. Ngay lập tức, bạn có cảm giác ngờ ngợ, khó chịu. Khi xem code, ta cũng có những phản ứng tương tự. Lúc đó, ta đánh hơi thấy mùi hôi thối của đoạn mã xấu.

Code Smells có thể là nguyên nhân dẫn đến nhưng vấn đề hay bug sâu hơn, khó tìm hơn, và với việc loại bỏ chúng thì ta có thể biết code của chúng ta sạch sẽ và tinh tế hơn. Code Smells thường gặp nhất là class dài loằng ngoằng, method quá to, code ko dùng đến hay lặp code. Những vấn đề này có thể sẽ không sinh ra lỗi ngay lập tức nhưng chúng có thể khiến chúng ta khó maintain cũng như có thể gây ra cả lỗi về sau. Sau đây mình xin đưa ra một vài đoạn code thối, bao gồm nhưng đoạn code thừa thãi, điều kiện quá dài, class có thuộc tính nhưng không có phương thức và tên biến không hợp lý.

1. Code thừa: if ... return true, else return false

Đoạn pattern if ... return true, else return false xuất hiện rất rất nhiều lần trong code của chúng ta. Trả về true hoặc false từ câu if là một việc khá thừa thãi (xem ví dụ bên dưới). Cách viết code như thế này là ví dụ điển hình cho việc code thừa và điều này sẽ dẫn đến việc project của chúng ta bị phát tướng và trở nên khó đọc hơn. Mình sẽ đưa ra hai cách viết code cho pattern này, một đoạn vừa dài vừa thừa và một đoạn ngắn gọn, tinh tế (?). Đây là một hàm trong class Animal để kiểm tra xem nó có phải là con mèo hay không.

class Animal
	attr_reader :type
	
	def initialize(type)
		@type = type
	end

	def is_a_cat?
		if type == "cat"
			return true
		else
			return false
		end
	end
end

Các bạn có ngửi thấy mùi thum thủm không? ĐIều kiện type == "cat" vốn đã trả về kiểu boolean rồi nên chúng ta không cần phải thêm nó vào câu điều kiện if :

class Animal
	attr_reader :type
	
	def initialize(type)
		@type = type
	end

	def is_a_cat?(animal)
		type == "cat"
	end
end

Yeah, thơm hơn rồi đó. Các bạn khi dùng pattern if ... return true, else return false chỉ cần nhớ rằng loại bỏ code thừa thì chúng ta sẽ có một đoạn code sạch đẹp và dễ đọc hơn nhiều. Tiếp tục thôi nào.

2. Câu điều kiện quá dàiiiiiiiiiiiiii

Nhiều lúc chúng ta sẽ phải check xem biến của chúng ta có bằng với một trong rất nhiều options khác nhau, có khá nhiều cách để làm việc này, nhưng một số cách lại "thối" hơn nhiều so với các cách còn lại.

puts "What is your major?"
major = gets.chomp

case major
when "Biology"
	puts "Mmm the study of life itself!"
when "Computer Science"
	puts "I'm a computer!"
when "English"
	puts "No way! What's your favorite book?"
when "Math"
	puts "Sweet! I'm great with numbers!"
else
	puts "That's a cool major!"
end

Cách này vẫn còn khá dài và khó đọc, thử cách này xem:

puts "What is your major?"
major = gets.chomp

# Set default response
major_responses = Hash.new("That's a cool major!")

# Add other responses
major_responses["Biology"] = "Mmm the study of life itself!"
major_responses["Computer Science"] = "I'm a computer!"
major_responses["English"] = "No way! What's your favorite book?"
major_responses["Math"] = "Sweet! I'm great with numbers!"

puts major_responses[major]

Bằng cách map major với response, chúng ta đã có 1 hash các major với response tương ứng. Chúng ta cũng tạo ra 1 giá trị mặc định để trả về khi môn học ta nhập vào không tồn tại trong hash major_responses. Sau khi tạo mapper cho major/response chúng ta chỉ cần 1 dòng code là có thể trả về đc kết quả tương ứng. Hãy nhớ là đây chỉ là 1 cách để tối ưu code,không phải là cách hợp lý và hiệu quả nhất.

3. Class có thuộc tính nhưng không có method

Như chúng ta đã biết, Class dùng để tổ chức lại các object có chung tính chất và biểu hiện. Nhưng sẽ ra sao nên chúng ta có 1 class không có method hay action nào ?

class Person
	attr_reader: :height, :hair_color, :dominant_hand, :iq, :race
	def initialize(height, hair_color, dominant_hand, iq, race)
		@height = height
		@hair_color = hair_color
		@dominant_hand = dominant_hand
		@iq = iq
		@race = race
	end
end

Như ta thấy, 1 Person có nhiều thuộc tính, nhưng trong ví dụ trên, 1 Person không hề có action nào hết. Vì thế mà, 1 class ko phải là cách hay nhất để thể hiện cho object Person này, cách hay và phù hợp hơn là dùng struct. struct được dùng để tạo ra các class đơn giản và được khởi tạo một cách chính xác hơn. Chúng ta có thể tạo 1 struct như sau:

Struct.new("Person", :height, :hair_color, :dominant_hand, :iq, :race)

Hoặc,

Person = Struct.new(:height, :hair_color, :dominant_hand, :iq, :race)

Và chúng ta tạo 1 object Person như sau:

nam = Person.new(170, "black", :right, 140, "asian")

Và thuộc tính của nam có thể được truy cập giống như với 1 class

nam.height  # 170
nam.hair_color # "black"
nam.dominant_hand # :right
nam.iq # 140
nam.race # "asian"

Từ nay chúng ta có thể "khử mùi" code của ta bằng cách sử dụng struct khi chúng có một object hoàn toàn có thể tự định nghĩa nó chỉ với các thuộc tính mà không có action hay method nào. Note thêm là struct khá hữu dụng cho việc xử lý các note-based data structure như list, tree hay graph.

4. Đặt tên biến

Hãy đặt tên biến của bạn một các phù hợp, chúng ta đã nghe thấy vấn đề này rất rất nhiều lần rồi. Trong khi x, y, hay a , b , c rất tiện và đỡ mất công nghĩ tên nhưng để sau này đọc lại code của bạn thì đúng là ác mộng, khiến cho việc bảo trì hay maintain project khó khăn hơn nhiều.

A = :cat
B = :dog
C = :bird

class Thing
	attr_reader :x
	
	def initialize(x)
		@x = x
	end

	def lightbulb
		if x == A || x == B || x == C
			puts "Animal !!"
		else
			puts "Nah !!"
		end
	end
end

VÍ dụ bên trên là biểu hiện rõ nhât của việc đặt tên biến khó hiểu. x là gì ? A, B, C là cái gì nhỉ, ai mà biết được. Cho nên hãy rõ ràng trong việc đặt tên biến, phương thức hay object, ... Điều này giúp đồng nghiệp của bạn có thể hiểu được đoạn code đó cũng như dễ cho chúng ta quay về debug hơn.

class Thing
		attr_reader :type
	
		def initialize(type)
			@type = type
		end

	def is_an_animal?
		if type == :cat || type == :dog || type == :bird
			puts "Animal !!"
		else
			puts "Nah !!"
		end
	end
end

5. Trùng lặp code

def post_to_site(data)
  url = build_url(data)
  response = RestClient.post(url)
end

def get_from_site(data)
  url = build_url(data)
  response = RestClient.get(url)
end

def delete_from_site(data)
  url = build_url(data)
  response = RestClient.delete(url)
end

Bạn có thể sửa lại đoạn code này bằng một câu lệnh meta-programming đơn giản:

def  response_from_site(data, method = :get)
  url = build_url(data)
  response = RestClient.public_send(method, url)
end

Đó, sạch sẽ - thơm tho - dễ đọc - dễ hiểu (?)

Túm lại

Qua bài viết này chúng ta đã có thể hiểu đc một số kiểu "thối" của code Ruby và cách "khử mùi" cho chúng. Chỉ cần phân tích và hiểu được cơ bản của đoạn code để chúng ta có thể sửa chúng và biến code của chúng ta rõ ràng hơn và một phần cũng cải thiện đc phong cách code của chúng ta.

0