30/09/2018, 20:29

Nên chọn kiểu viết code nào trong Java?

Số là bọn em đang làm bài tập. Đề bài cụ thể là: “Viết chương trình nhận vào một chuỗi bất kì và in ra chuỗi đó sau khi đã được viết hoa chữ cái đầu tiên mỗi từ. Nếu từ bắt đầu bằng kí tự đặc biệt thì từ đó không phải viết hoa kí tự nào. Không được dùng phương thức toUpperCase() có sẵn, hãy viết (các) phương thức chính để giải quyết vấn đề trên.”

Em viết như thế này:

public static boolean isInRange(char chr) {
	return ((int) chr >= 'a' && (int) chr <= 'z');
}

public static char uppercaseAChar(char chr) {
	if (isInRange(chr))
		return (char) (chr - 32);
	return chr;
}

public static String uppercaseEachWord(String str) {
	if (str == null || str.length() == 0)
		return str;
	StringBuilder res = new StringBuilder();
	res.append(uppercaseAChar(str.charAt(0)));
	for (int i = 1; i < str.length(); i++) {
		if (str.charAt(i - 1) == ' ') {
			res.append(uppercaseAChar(str.charAt(i)));
		} else
			res.append(str.charAt(i));
	}
	return res.toString();
}

Còn bạn em viết như thế này:

private static String inHoaCacChuDauTungTu(String s2) {
	char[] arr = s2.toCharArray();
	for (int i = 0; i < arr.length; i++) {
		if (i == 0 && (int) arr[i] >= 97 && (int) arr[i] <= 122) {
			arr[i] = (char) ((int) arr[i] - 32);
		}
		if ((int) arr[i] == 32) {
			if (i < arr.length - 1 && (int) arr[i + 1] >= 97 && (int) arr[i + 1] <= 122)
				arr[i + 1] = (char) ((int) arr[i + 1] - 32);
			if (i == arr.length - 1)
				break;
		}
	}
	String s = new String(arr);
	return s;
}

Thật sự thì khi test code của nó chạy nhanh hơn của em. Nhưng em thấy code của nó nhằng nhèo sao ấy, khó đọc quá. Theo các ACE, nên chọn cách viết code nào ạ? Có phải em chia ra thành các phương thức riêng lẻ nên nó bị chậm không? Mong các ACE giải đáp giúp. Em cảm ơn nhiều ạ!!!

nonStop viết 22:36 ngày 30/09/2018

Tùy , nếu chương trình chỉ chạy trên client thì nên để kiểu của bạn vì dễ maintain , nhưng nếu code một ứng dụng web có 1000 người dùng chẳng hạn , bạn nên để ý đến tốc độ và code kiểu thứ 2 , code phức tạp một chút , nhưng cố gắng comment thật dễ hiểu là ok . code đẹp nhiều khi không phải là dễ đọc đâu , code đẹp là chạy hiệu quả sau đó thì đơn giản nhất có thể .

Mai Thế Nguyễn viết 22:30 ngày 30/09/2018

Sao thầy em dạy StringBuilder nhanh hơn StringBuffer ạ?

Mirana viết 22:32 ngày 30/09/2018

em gà nhưng theo em biết thì đệ quy bao giờ cũng chạy chậm hơn vòng lặp

Mai Thế Nguyễn viết 22:35 ngày 30/09/2018

Code mình đâu có dùng đê quy đâu bạn.

Mirana viết 22:41 ngày 30/09/2018

vậy chắc gọi hàm thì chậm hơn

Đỗ Trung Quân viết 22:37 ngày 30/09/2018

StringBuffer is synchronized, StringBuilder is not.

Mai Thế Nguyễn viết 22:36 ngày 30/09/2018

Không phải sync nên nó nhanh hơn đúng không ạ?

nonStop viết 22:35 ngày 30/09/2018

StringBuffer thuộc loại synchronized , giúp xử lý đa luồng , có bộ đệm tránh xung đột . nếu không làm về đa luồng thì đừng có dùng buffer , không lại nặng code ra .

nonStop viết 22:31 ngày 30/09/2018

Code của bạn , khi chương trình complie đường đi sẽ loằng ngằng hơn khối có for vì thế chậm hơn là đúng rồi .

Tobi the Terrible viết 22:37 ngày 30/09/2018

Mình thấy cách viết code của bạn dễ đọc hơn + có lợi về lâu dài. Giả sử sau này làm việc theo nhóm, hoặc 1 năm sau bạn quay trở lại đọc 2 đoạn code bên dưới, bạn nghĩ đoạn nào đọc dễ hiểu hơn? Dự án thực tế thường kéo dài nhiều năm (1-2 năm dev, 5+ năm bảo trì), nên 1 đoạn code qua tay nhiều người là chuyện bình thường.

Bạn nói đoạn code thứ 2 chạy nhanh hơn, nhưng nhanh hơn có đáng kể không? Theo mình thấy thì cả 2 đoạn này đều là O(n) hết, nhanh hơn không đáng kể thì nên bỏ qua (premature optimization is the root of all evil). Mình nghĩ nguyên nhân code của bạn chậm hơn là do thao tác trực tiếp trên String chứ không phải char[]. Bạn thử thay char[] arr = str.toCharArray(); xem tốc độ có cải thiện không? Bạn có thể dùng VisualVM để benchmark code, thấy chỗ nào chậm thì optimize chỗ đó

Về phần StringBuffer & StringBuilder, trong trường hợp này mình nghĩ nên dùng StringBuilder, tại vì theo như code ở trên, dùng cái nào thì khi chạy cũng chỉ có 1 thread.

  • StringBuffer thường sử dụng khi có nhiều thread dùng chung 1 object StringBuffer (chứ không phải nó tự tách ra 1 luồng riêng rồi chạy)
  • StringBuilder thì chỉ dùng nội bộ trong 1 thread, thread nào tạo thì thread đó dùng

Về cách code sao cho đẹp, dễ đọc. Bạn có thể đọc Clean Code, sách này viết ví dụ bằng Java nhưng có thể ứng dụng qua các ngôn ngữ khác.

Java Virtual Machine, lúc compile còn có một bước optimize bytecode nữa, nên chưa hẳn gọi nhiều method sẽ lâu hơn. Mình không rõ vụ optimize lắm nên cũng không dám chém

viết 22:43 ngày 30/09/2018

cách 1 dễ đọc hơn.

cách 2 có 2 lần viết lại
arr[i] = (char) ((int) arr[i] - 32);
arr[i + 1] = (char) ((int) arr[i + 1] - 32);

(int) arr[i] >= 97 && (int) arr[i] <= 122
(int) arr[i + 1] >= 97 && (int) arr[i + 1] <= 122
nên tách thành 2 phương thức riêng chứ đừng copy paste thế này. Nếu copy paste thì khi debug phải tìm tất cả những chỗ đã copy paste mà sửa, rất vất vả. Nếu tách thành 1 phương thức riêng thì chỉ cần sửa phương thức này là xong.

ngoài ra
(int) arr[i] == 32
có lẽ nghiện C quá… mà C cũng ko bệnh thế này. arr[i] == ' ' dễ đọc hơn sao lại cast thành int rồi so sánh với 32 làm gì. Tương tự với >= 97 và <= 122. Khỏi cần cast về int. Viết arr[i] >= 'a' && arr[i] <= 'z' và gọn vừa dễ đọc.

ngoài ra nữa:
if (i == arr.length - 1) break;
ko cần thiết.

ngoài ra thì

String s = new String(arr);
return s;

viết luôn là return new String(arr); luôn cho gọn.

anon10499953 viết 22:32 ngày 30/09/2018

Viết theo cách của bạn là được rồi. Đừng cho một cái hàm ôm đồm quá nhiều thứ, sau này phải chỉnh sửa thì rất khó khăn…

Van Huong viết 22:31 ngày 30/09/2018

Đồng ý với cách viết nên tách hàm ra cho dễ đọc hơn. Hơn thế nữa, để cuộc sống dễ dàng hơn thì tôi bổ sung ý kiến chúng ta nên tránh harded code.

  1. hàm isInRange(char chr) có thể thay bằng java.lang.Character.isLetter(char ch) sẽ bổ rẻ hơn.
  2. số 32 có thể thay bằng một constant, ví dụ: private static final int OFFSET = 'a' - 'A';
  3. kiểm tra kí tự khoảng trắng str.charAt(i - 1) == ' ' có thể thay bằng Character.isWhitespace(str.charAt(i - 1))

Bạn nghĩ sao?

public class StringUtils {
	private static final int OFFSET = 'a' - 'A';
	private static final int FIRST_INDEX = 0;

	public static String upperCaseWords(String source) {
		char[] characters = source.toCharArray();
		
		for (int i = FIRST_INDEX; i < characters.length; i++) {
			if(Character.isLetter(characters[i]) 
				&& !Character.isUpperCase(characters[i])){
				if(i == FIRST_INDEX){
					convertToUpperCaseChar(characters, i);
				}else if(Character.isWhitespace(characters[i-1])){
					convertToUpperCaseChar(characters, i);
				}
			}
		}
		
		return new String(characters);
	}

	private static void convertToUpperCaseChar(char[] characters, int i) {
		characters[i] = (char) (characters[i] - OFFSET);
	}
}
Đỗ Trung Quân viết 22:45 ngày 30/09/2018

Nếu nói là nên chọn kiểu nào thì hãy chọn kiểu này

Matcher m = Pattern.compile("([a-z])([a-z]*)"

=))))

Mai Thế Nguyễn viết 22:37 ngày 30/09/2018

Cảm ơn anh nhiều ạ! Code style chất quá. Em nhất định sẽ áp dụng.

Mai Thế Nguyễn viết 22:43 ngày 30/09/2018

Xin anh vui lòng giải thích thêm

Van Huong viết 22:41 ngày 30/09/2018

thêm điều kiện là nếu kí tự đã in hoa rồi thì không làm gì cả. Character.isUpperCase(characters[i])

Đỗ Trung Quân viết 22:35 ngày 30/09/2018

Pattern- Regular Expressions

Mai Thế Nguyễn viết 22:36 ngày 30/09/2018

Cảm ơn anh, em lại được học thêm 1 thứ bổ ích.

Phan Hoàng viết 22:42 ngày 30/09/2018

Mình thì theo quan điểm không có đúng sai trong lập trình, nên cân bằng giữa tính hiệu quả và tính dễ đọc. Cách 1 của bạn dễ đọc, dễ maintain, tuy phải đánh đổi việc gọi function chéo nhau (tất nhiên sẽ chậm hơn) so với Cách 2 chạy nhanh hơn, nhưng sau maintain khó khăn. Nếu benchmark thì mình thấy cách 1 cũng không chậm hơn đáng kể, nhất là với các máy tính hiện nay, tốc độ CPU rất lớn, RAM khủng, … thì chắc vài micro micro… second ^^

Vấn đề viết code thế nào cho hiệu quả cũng là 1 chủ đề khá thú vị (người Nhật họ gọi là Coding Dojo, nghĩa là một đấu trường coding, ở đó họ tìm cách đạt 1 mục đích với nhiều cách thức khác nhau để tìm ra đường kiếm đẹp nhất và hiệu quả nhất. Riêng dân Nhật, cái gì cũng đẩy lên thành 1 thứ đạo, ví dụ riêng chuyện uống trà -> trà đạo)

Ví dụ bài toán FlipFlop khi chia hết cho 3, 5 và cả 3 và 5 (chia hết cho 15)

Cách 1

function printFlipFlop(n){
	for ($i=0; $i<=n;$i++){
		if (($i % 3 == 0) && ($i%5 == 0)){
			echo "FlipFlop";
			continue;
		}

		if ($i % 3 == 0) {
			echo "Flip";
		}

		if ($i % 5 == 0) {
			echo "Flop";
		}
	}
}

Cách 2:

function printFlipFop($n){
 for ($i=0; $i<=$n;$i++){
  if (($i % 3 == 0) && ($i % 5 == 0)){
      echo "$i FlipFlop ";
      continue;
  }

  if ($i % 3 == 0) {
   echo "$i Flip ";
  }

  if ($i % 5 == 0) {
   echo "$i Flop ";
  }
 }
}

Cách 3:

$FLIP_NUMBER = 3;
$FLOP_NUMBER = 5;

function printFLIP($n){
	for ($i=0; $i<=n; $i++){
		if ($i % $FLIP_NUMBER == 0){
			echo "$i Flip";
                        continue;
		}
	}
}

function printFlop($n){
	for ($i=0; $i<=n; $i++){
		if ($i % $FLOP_NUMBER == 0){
			echo "$i Flop";
                        continue;
		}
	}
}

function printFlipFlop($n){
	for ($i=0; $i<=n; $i++){
		if (($i % $FLOP_NUMBER == 0) && ($i % $FLIP_NUMBER == 0)){
			echo "$i FlipFlop";
                        continue;
		}
	}
}

printFLIP(30);
printFlop(30);
printFlipFlop(30);

Cách 4:

$FLIP_NUMBER = 3;
$FLOP_NUMBER = 5;

function printFLIP($n){
	for ($i=0; $i<=n; $i++){
		if (isFlipNumber($i)){
			echo "$i Flip";
			continue;
		}
	}
}

function printFlop($n){
	for ($i=0; $i<=n; $i++){
		if (isFlopNumber($i)){
			echo "$i Flop";
			continue;
		}
	}
}

function printFlipFlop($n){
	for ($i=0; $i<=n; $i++){
		if (isFlipFlopNumber($i)){
			echo "$i FlipFlop";
			continue;
		}
	}
}

function isFlipNumber($i){
	return ($i % $FLIP_NUMBER == 0);
}

function isFlopNumber($i){
	return ($i % $FLOP_NUMBER == 0);
}

function isFlipFlopNumber($i){
	return isFlipNumber($i) && isFlopNumber($i);
}

printFLIP(30);
printFlop(30);
printFlipFlop(30);

Cách 5:

function printFlipFlop($n){
	for ($i=0; $i<=n; $i++){
		$is_flip_number = $i % 3;
		$is_flop_number = $i % 5;
		$is_flip_flop_number = $is_flip_number && $is_flop_number;

		
		if ($is_flip_flop_number){
			echo "$i flipflop";
			continue;
		}

		if ($is_flip_number){
			echo "$i flip ";
			continue;
		}

		if ($is_flop_number){
			echo "$i flop ";
			continue;
		}

	}
}

Cách 6:

function printFlipFlop($n){
	for ($i=0; $i<=n; $i++){
		echo getMagicString($i);
	}
}

function getMagicString($i){
	        $is_flip_number = $i % 3;
		$is_flop_number = $i % 5;
		$is_flip_flop_number = $is_flip_number && $is_flop_number;
		
		if ($is_flip_flop_number){
			return "$i flipflop";
		}

		if ($is_flip_number){
			return "$i flip ";
		}

		if ($is_flop_number){
			return "$i flop ";
		}

		return $i
		
}

Bạn thấy cách nào hiệu quả và tại sao?

Bài liên quan
0