Clean Code - Tôi có nên đổi chữ 1 thành hằng số không?


14

Để tránh những con số ma thuật, chúng ta thường nghe rằng chúng ta nên đặt một cái tên theo nghĩa đen. Nhu la:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Tôi có một phương pháp giả như thế này:

Giải thích: Tôi cho rằng tôi có một danh sách những người cần phục vụ và theo mặc định, chúng tôi chỉ chi 5 đô la để mua thức ăn, nhưng khi chúng tôi có nhiều hơn một người, chúng tôi cần mua nước và thực phẩm, chúng tôi phải chi nhiều tiền hơn, có thể là 6 đô la. Tôi sẽ thay đổi mã của mình, vui lòng tập trung vào nghĩa đen 1 , câu hỏi của tôi về nó.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Khi tôi yêu cầu bạn bè xem lại mã của mình, một người nói rằng đặt tên cho giá trị 1 sẽ mang lại mã sạch hơn và người còn lại nói rằng chúng tôi không cần một tên liên tục ở đây vì bản thân giá trị đó có ý nghĩa.

Vì vậy, câu hỏi của tôi là tôi có nên đặt tên cho giá trị theo nghĩa đen 1 không? Khi nào một giá trị là một con số kỳ diệu và khi nào nó không? Làm thế nào tôi có thể phân biệt bối cảnh để chọn giải pháp tốt nhất?


personsđến từ đâu và nó mô tả cái gì? Mã của bạn không có ý kiến ​​gì vì vậy thật khó để đoán nó đang làm gì.
Hubert Grzeskowiak

7
Nó không nhận được bất kỳ rõ ràng hơn 1. Tôi sẽ thay đổi "kích thước" thành "đếm" mặc dù. Và moneyService là ngu ngốc như vậy, nó sẽ có thể quyết định số tiền sẽ trả lại tùy thuộc vào bộ sưu tập của mọi người. Vì vậy, tôi sẽ chuyển người đến getMoney và để nó phân loại bất kỳ trường hợp ngoại lệ nào.
Martin Maat

4
Bạn cũng có thể trích xuất biểu thức logic từ mệnh đề if của bạn sang một phương thức mới. Có thể gọi nó là IsSinglePerson (). Bằng cách đó, bạn trích xuất nhiều hơn một chút so với chỉ một biến đó mà còn làm cho mệnh đề if dễ đọc hơn một chút ...
selmaohneh


2
Có lẽ logic này sẽ phù hợp hơn trong moneyService.getMoney ()? Có khi nào bạn cần gọi getMoney trong trường hợp 1 người không? Nhưng tôi đồng ý với tình cảm chung rằng 1 là rõ ràng. Số ma thuật là những con số mà bạn phải gãi đầu hỏi làm thế nào lập trình viên đến số đó .. tức làif(getErrorCode().equals(4095)) ...
Neil

Câu trả lời:


23

Không. Trong ví dụ đó, 1 là hoàn toàn có ý nghĩa.

Tuy nhiên, nếu people.size () bằng 0 thì sao? Có vẻ lạ mà persons.getMoney()hoạt động cho 0 và 2 nhưng không phải cho 1.


Tôi đồng ý rằng 1 là có ý nghĩa. Cảm ơn, nhân tiện, tôi đã cập nhật câu hỏi của mình. Bạn có thể nhìn thấy nó một lần nữa để có được ý tưởng của tôi.
Jack

3
Một cụm từ tôi đã nghe nhiều lần trong nhiều năm qua là các số ma thuật là bất kỳ hằng số không tên nào khác 0 và 1. Trong khi tôi có thể nghĩ về các ví dụ trong đó các số này nên được đặt tên, đó là một quy tắc khá đơn giản để tuân theo 99% thời gian.
Baldrickk

@Baldrickk Đôi khi, những chữ số khác cũng không nên bị ẩn đằng sau một cái tên.
Ded repeatator

@Ded repeatator có ví dụ nào xuất hiện trong tâm trí?
Baldrickk

@Baldrickk Xem amon .
Ded repeatator

19

Tại sao một đoạn mã chứa giá trị theo nghĩa đen cụ thể đó?

  • Liệu giá trị này có một ý nghĩa đặc biệt trong miền vấn đề ?
  • Hoặc giá trị này chỉ là một chi tiết triển khai , trong đó giá trị đó là hệ quả trực tiếp của mã xung quanh?

Nếu giá trị theo nghĩa đen có nghĩa không rõ ràng từ ngữ cảnh, thì có, việc đặt giá trị đó cho một tên thông qua một hằng hoặc biến là hữu ích. Sau này, khi bối cảnh ban đầu bị lãng quên, mã với các tên biến có ý nghĩa sẽ được duy trì nhiều hơn. Hãy nhớ rằng, đối tượng cho mã của bạn không phải chủ yếu là trình biên dịch (trình biên dịch sẽ vui vẻ làm việc với mã khủng khiếp), nhưng những người duy trì tương lai của mã đó - những người sẽ đánh giá cao nếu mã có phần tự giải thích.

  • Trong ví dụ đầu tiên của bạn, ý nghĩa của các chữ như 34, 4, 5là không rõ ràng từ ngữ cảnh. Thay vào đó, một số giá trị này có ý nghĩa đặc biệt trong miền vấn đề của bạn. Do đó, thật tốt khi đặt tên cho họ.

  • Trong ví dụ thứ hai của bạn, ý nghĩa của nghĩa đen 1rất rõ ràng từ ngữ cảnh. Giới thiệu một cái tên là không hữu ích.

Trong thực tế, việc giới thiệu tên cho các giá trị rõ ràng cũng có thể xấu vì nó che giấu giá trị thực tế.

  • Điều này có thể làm mờ các lỗi nếu giá trị được đặt tên bị thay đổi hoặc không chính xác, đặc biệt nếu cùng một biến được sử dụng lại trong các đoạn mã không liên quan.

  • Một đoạn mã cũng có thể hoạt động tốt với một giá trị cụ thể, nhưng có thể không chính xác trong trường hợp chung. Bằng cách giới thiệu sự trừu tượng không cần thiết, mã không còn rõ ràng chính xác nữa.

Không có giới hạn về kích thước đối với các chữ có nghĩa là rõ ràng bởi vì điều này phụ thuộc hoàn toàn vào bối cảnh. Ví dụ: nghĩa đen 1024có thể hoàn toàn rõ ràng trong ngữ cảnh tính toán kích thước tệp hoặc nghĩa đen 31trong ngữ cảnh của hàm băm hoặc nghĩa đen padding: 0.5emtrong ngữ cảnh của biểu định kiểu CSS.


8

Có một số vấn đề với đoạn mã này, nhân tiện, có thể rút ngắn như thế này:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Không rõ tại sao một người là một trường hợp đặc biệt. Tôi cho rằng có một quy tắc kinh doanh cụ thể cho biết rằng nhận tiền từ một người hoàn toàn khác với nhận tiền từ nhiều người. Tuy nhiên, tôi phải đi và nhìn vào bên trong cả hai getMoneyIfHasOnePerson, và getMoneyhy vọng hiểu tại sao có những trường hợp khác biệt.

  2. Cái tên getMoneyIfHasOnePersonkhông đúng. Từ tên, tôi sẽ mong đợi phương pháp kiểm tra nếu có một người duy nhất và, nếu đây là trường hợp, hãy lấy tiền từ anh ta; nếu không thì không làm gì cả Từ mã của bạn, đây không phải là điều đang xảy ra (hoặc bạn đang làm điều kiện hai lần).

  3. Có bất kỳ lý do để trả lại một List<Money>thay vì một bộ sưu tập?

Quay lại câu hỏi của bạn, không rõ lý do tại sao có một điều trị đặc biệt cho một người, nên thay thế một chữ số bằng một hằng số, trừ khi có một cách khác để làm cho các quy tắc rõ ràng. Ở đây, một người không khác lắm so với bất kỳ số ma thuật nào khác. Bạn có thể có các quy tắc kinh doanh nói rằng việc đối xử đặc biệt áp dụng cho một, hai hoặc ba người, hoặc chỉ với hơn mười hai người.

Làm thế nào tôi có thể phân biệt bối cảnh để chọn giải pháp tốt nhất?

Bạn làm bất cứ điều gì làm cho mã của bạn rõ ràng hơn.

ví dụ 1

Hãy tưởng tượng đoạn mã sau:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Không có ở đây một giá trị ma thuật? Mã này khá rõ ràng: nếu không có phần tử nào trong chuỗi, chúng ta không xử lý nó và trả về một giá trị đặc biệt. Nhưng mã này cũng có thể được viết lại như thế này:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Ở đây, không còn hằng số, và mã thậm chí còn rõ ràng hơn.

Ví dụ 2

Lấy một đoạn mã khác:

const result = Math.round(input * 1000) / 1000;

Không mất quá nhiều thời gian để hiểu những gì nó làm trong các ngôn ngữ như JavaScript không bị round(value, precision)quá tải.

Bây giờ, nếu bạn muốn giới thiệu một hằng số, nó sẽ được gọi như thế nào? Thuật ngữ gần nhất bạn có thể nhận được là Precision. Vì thế:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Nó cải thiện khả năng đọc? Có thể là. Ở đây, giá trị của hằng số khá hạn chế và bạn có thể tự hỏi mình có thực sự cần thực hiện tái cấu trúc không. Điều tuyệt vời ở đây là bây giờ, độ chính xác chỉ được khai báo một lần, vì vậy nếu nó thay đổi, bạn không có nguy cơ mắc lỗi như:

const result = Math.round(input * 100) / 1000;

thay đổi giá trị ở một vị trí và quên làm điều đó ở một vị trí khác.

Ví dụ 3

Từ những ví dụ đó, bạn có thể có ấn tượng rằng các số phải được thay thế bằng hằng số trong mọi trường hợp . Đây không phải là sự thật. Trong một số trường hợp, việc có một hằng số không dẫn đến cải tiến mã.

Lấy đoạn mã sau:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Nếu bạn cố gắng thay thế số không bằng một biến, khó khăn sẽ là tìm một tên có ý nghĩa. Làm thế nào bạn sẽ đặt tên cho nó? ZeroPosition? Base? Default? Giới thiệu một hằng số ở đây sẽ không tăng cường mã theo bất kỳ cách nào. Nó sẽ làm cho nó dài hơn một chút, và chỉ vậy thôi.

Những trường hợp như vậy rất hiếm. Vì vậy, bất cứ khi nào bạn tìm thấy một số trong mã, hãy nỗ lực tìm cách làm thế nào mã có thể được tái cấu trúc. Hãy tự hỏi nếu có một ý nghĩa kinh doanh cho số. Nếu có, một hằng số là bắt buộc. Nếu không, bạn sẽ đặt tên cho số như thế nào? Nếu bạn tìm thấy một cái tên có ý nghĩa, đó là tuyệt vời. Nếu không, rất có thể bạn đã tìm thấy một trường hợp mà hằng số là không cần thiết.


Tôi sẽ thêm 3a: Đừng sử dụng tiền từ trong tên biến, sử dụng số tiền, số dư hoặc tương tự. Một bộ sưu tập tiền không có ý nghĩa, bộ sưu tập số tiền hoặc số dư nào. (OK tôi có một bộ sưu tập nhỏ tiền nước ngoài (hay đúng hơn là tiền xu) nhưng đó là một vấn đề phi lập trình khác).
Bent

3

Bạn có thể tạo một hàm lấy một tham số duy nhất và trả về bốn lần chia cho năm cung cấp bí danh rõ ràng cho những gì nó làm trong khi vẫn sử dụng ví dụ đầu tiên.

Tôi chỉ đưa ra chiến lược thông thường của riêng mình nhưng có lẽ tôi cũng sẽ học được điều gì đó.

Những gì tôi đang nghĩ là.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Xin lỗi nếu tôi không có cơ sở nhưng tôi chỉ là một nhà phát triển javascript. Tôi không chắc thành phần nào tôi đã biện minh cho điều này, tôi đoán không phải mọi thứ phải nằm trong một mảng hoặc danh sách nhưng .length sẽ rất có ý nghĩa. Và * 4 sẽ làm cho hằng số tốt vì nguồn gốc của nó là không rõ ràng.


5
Nhưng những giá trị 4 và 5 đó có ý nghĩa gì? Nếu một trong số họ cần thay đổi, bạn có thể tìm thấy tất cả các địa điểm mà số được sử dụng trong bối cảnh tương tự và cập nhật các vị trí đó cho phù hợp không? Đó là mấu chốt trong câu hỏi ban đầu.
Bart van Ingen Schenau

Tôi nghĩ rằng ví dụ đầu tiên là khá tự giải thích để có thể không phải là một câu tôi có thể trả lời. Hoạt động sẽ không thay đổi trong tương lai, khi bạn cần viết một cái mới để hoàn thành những gì cần thiết. Nhận xét là những gì tôi nghĩ sẽ có lợi nhất cho mục đích này. Đó là một cuộc gọi một dòng để giảm ngôn ngữ của tôi. Làm thế nào là quan trọng để làm cho hoàn toàn dễ hiểu cho giáo dân?
thẩm quyền

@BartvanIngenSchenau Toàn bộ chức năng được đặt tên không chính xác. Tôi thích một tên hàm tốt hơn, một bình luận với thông số kỹ thuật mà hàm được cho là trả về và một bình luận tại sao * 4/5 đạt được điều đó. Tên liên tục không thực sự cần thiết.
gnasher729

@ gnasher729: Nếu các hằng số xuất hiện chính xác một lần trong mã nguồn của hàm được đặt tên tốt, có tài liệu tốt, thì có thể không cần sử dụng hằng số được đặt tên. Ngay khi một hằng số xuất hiện nhiều lần với cùng một nghĩa, việc đặt tên hằng đó đảm bảo rằng bạn không phải tìm hiểu xem tất cả các trường hợp của nghĩa đen 42 có nghĩa giống nhau hay không.
Bart van Ingen Schenau

Tại trung tâm của nó, nếu bạn mong đợi bạn có thể cần phải thay đổi giá trị có. Tôi sẽ thay đổi nó thành một const để đại diện cho biến, tuy nhiên. Tôi không nghĩ đó là trường hợp và điều này chỉ đơn giản là một biến trung gian cư trú trong phạm vi trên nó. Tôi không muốn sao chép và nói biến mọi thứ thành tham số cho hàm nhưng nếu nó có thể thay đổi nhưng hiếm khi tôi biến nó thành tham số bên trong cho phạm vi hàm hoặc đối tượng / cấu trúc đó nơi bạn hiện đang làm việc để thay đổi đó rời đi phạm vi toàn cầu một mình.
thẩm quyền

2

Đó là số 1, nó có thể là một số khác? Nó có thể là 2, hoặc 3, hoặc có những lý do hợp lý tại sao nó phải là 1? Nếu nó phải là 1, thì sử dụng 1 là ổn. Nếu không, bạn có thể định nghĩa một hằng số. Đừng gọi đó là MỘT. (Tôi đã thấy điều đó được thực hiện).

60 giây trong một phút - bạn có cần một hằng số không? Vâng, đó 60 giây, không phải 50 hay 70. Và mọi người đều biết điều đó. Vì vậy, có thể ở lại một số.

60 mục được in trên mỗi trang - con số đó có thể dễ dàng là 59 hoặc 55 hoặc 70. Trên thực tế, nếu bạn thay đổi kích thước phông chữ, nó có thể trở thành 55 hoặc 70. Vì vậy, ở đây một hằng số có ý nghĩa được yêu cầu nhiều hơn.

Đó cũng là một vấn đề rõ ràng như thế nào là ý nghĩa. Nếu bạn viết "phút = giây / 60", điều đó rõ ràng. Nếu bạn viết "x = y / 60", điều đó không rõ ràng. Phải có một số tên có ý nghĩa ở đâu đó.

Có một quy tắc tuyệt đối: Không có quy tắc tuyệt đối. Với thực hành, bạn sẽ tìm ra khi nào nên sử dụng số và khi nào được sử dụng hằng số có tên. Đừng làm điều đó bởi vì một cuốn sách nói như vậy - cho đến khi bạn hiểu tại sao nó nói điều đó.


"60 giây trong một phút ... Và mọi người đều biết điều đó. Vì vậy, nó có thể giữ nguyên một con số." - Không phải là một đối số hợp lệ với tôi. Điều gì xảy ra nếu tôi có 200 vị trí trong mã của mình trong đó "60" được sử dụng? Làm thế nào để tôi tìm thấy những nơi mà nó được sử dụng như giây đến phút so với các cách sử dụng "tự nhiên" khác có thể như nhau? - Tuy nhiên, trong thực tế, tôi cũng dám sử dụng minutes*60, đôi khi ngay cả hours*3600khi tôi cần mà không khai báo các hằng số bổ sung. Trong nhiều ngày, tôi có thể viết d*24*3600hoặc d*24*60*60vì ở 86400gần rìa nơi mà ai đó sẽ không nhận ra con số ma thuật đó trong nháy mắt.
JimmyB

@JimmyB Nếu bạn đang sử dụng "60" trên 200 vị trí trong mã của mình, rất có thể giải pháp đó là tái cấu trúc một hàm thay vì đưa ra hằng số.
klutt

0

Tôi đã thấy một số lượng mã khá lớn như trong OP (đã sửa đổi) trong các lần truy xuất DB. Truy vấn trả về một danh sách, nhưng các quy tắc kinh doanh cho biết có thể chỉ có một yếu tố. Và sau đó, tất nhiên, một cái gì đó đã thay đổi cho 'chỉ một trường hợp này' thành một danh sách có nhiều hơn một mục. (Vâng, tôi đã nói rất nhiều lần. Nó gần giống như họ ... nm)

Vì vậy, thay vì tạo một hằng số, tôi sẽ (trong phương thức mã sạch) tạo ra một phương thức để đặt tên hoặc làm rõ những gì điều kiện được dự định để phát hiện (và gói gọn cách nó phát hiện ra nó):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}

Tốt hơn nhiều để thống nhất xử lý. Một danh sách các yếu tố n có thể được xử lý thống nhất cho dù n là gì.
Ded repeatator

Tôi đã thấy rằng nó không phải, trong thực tế, trường hợp duy nhất là một giá trị (cận biên) khác với nó nếu cùng một người dùng là một phần của danh sách kích thước n. Trong một OO được thực hiện tốt, điều này có thể không phải là một vấn đề, nhưng, khi xử lý các hệ thống cũ, việc triển khai OO tốt không phải lúc nào cũng khả thi. Điều tốt nhất chúng tôi nhận được là một mặt tiền OO hợp lý trên logic DAO.
Kristian H
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.