Cơ sở lý luận để thích các biến cục bộ hơn các biến thể hiện?


109

Cơ sở mã hóa tôi đang làm việc thường xuyên sử dụng các biến đối tượng để chia sẻ dữ liệu giữa các phương thức tầm thường khác nhau. Nhà phát triển ban đầu rất kiên quyết rằng điều này tuân thủ các thực tiễn tốt nhất được nêu trong cuốn sách Clean Code của chú Bob / Robert Martin: "Quy tắc đầu tiên của các chức năng là chúng phải nhỏ." và "Số lượng đối số lý tưởng cho một hàm là 0 (niladic). (...) Các đối số là khó. Chúng có rất nhiều sức mạnh khái niệm."

Một ví dụ:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  private byte[] encodedData;
  private EncryptionInfo encryptionInfo;
  private EncryptedObject payloadOfResponse;
  private URI destinationURI;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    getEncodedData(encryptedRequest);
    getEncryptionInfo();
    getDestinationURI();
    passRequestToServiceClient();

    return cryptoService.encryptResponse(payloadOfResponse);
  }

  private void getEncodedData(EncryptedRequest encryptedRequest) {
    encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private void getEncryptionInfo() {
    encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
  }

  private void getDestinationURI() {
    destinationURI = router.getDestination().getUri();
  }

  private void passRequestToServiceClient() {
    payloadOfResponse = serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

Tôi sẽ cấu trúc lại thành các biến sau bằng cách sử dụng các biến cục bộ:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
    EncryptionInfo encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
    URI destinationURI = router.getDestination().getUri();
    EncryptedObject payloadOfResponse = serviceClient.handle(destinationURI, encodedData,
      encryptionInfo);

    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

Điều này ngắn hơn, nó loại bỏ việc ghép dữ liệu ngầm giữa các phương thức tầm thường khác nhau và nó giới hạn phạm vi biến đổi ở mức tối thiểu cần thiết. Tuy nhiên, bất chấp những lợi ích này, tôi dường như vẫn không thể thuyết phục được nhà phát triển ban đầu rằng việc tái cấu trúc này được bảo hành, vì nó dường như mâu thuẫn với các hoạt động của chú Bob đã đề cập ở trên.

Do đó câu hỏi của tôi: lý do khách quan, khoa học để ủng hộ các biến cục bộ hơn các biến thể hiện là gì? Tôi dường như không thể đặt ngón tay của mình lên nó. Trực giác của tôi nói với tôi rằng các khớp nối ẩn là xấu và phạm vi hẹp tốt hơn phạm vi rộng. Nhưng khoa học để sao lưu này là gì?

Và ngược lại, có bất kỳ nhược điểm nào đối với việc tái cấu trúc này mà tôi có thể bỏ qua không?


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

Câu trả lời:


170

Mục tiêu, cơ sở khoa học để ủng hộ các biến cục bộ hơn các biến thể hiện là gì?

Phạm vi không phải là trạng thái nhị phân, nó là một gradient. Bạn có thể xếp hạng những thứ này từ lớn nhất đến nhỏ nhất:

Global > Class > Local (method) > Local (code block, e.g. if, for, ...)

Chỉnh sửa: những gì tôi gọi là "phạm vi lớp" là những gì bạn có nghĩa là "biến thể hiện". Theo hiểu biết của tôi, đó là những từ đồng nghĩa, nhưng tôi là nhà phát triển C #, không phải nhà phát triển Java. Vì lợi ích của sự ngắn gọn, tôi đã gộp tất cả các số liệu thống kê vào danh mục toàn cầu vì các số liệu thống kê không phải là chủ đề của câu hỏi.

Phạm vi càng nhỏ thì càng tốt. Lý do là các biến nên sống trong phạm vi nhỏ nhất có thể . Có nhiều lợi ích cho việc này:

  • Nó buộc bạn phải suy nghĩ về trách nhiệm của lớp hiện tại và giúp bạn gắn bó với SRP.
  • Nó cho phép bạn không cần phải tránh những xung đột đặt tên toàn cầu, ví dụ như nếu hai hay nhiều lớp có một Nametài sản, bạn không bắt buộc tiền tố họ như FooName, BarName, ... Vì vậy, việc giữ tên biến của bạn như sạch và ngắn gọn càng tốt.
  • Nó khai báo mã bằng cách giới hạn các biến có sẵn (ví dụ như Intellisense) với các biến có liên quan theo ngữ cảnh.
  • Nó cho phép một số hình thức kiểm soát truy cập để dữ liệu của bạn không thể bị thao túng bởi một số diễn viên mà bạn không biết (ví dụ: một lớp khác được phát triển bởi một đồng nghiệp).
  • Nó làm cho mã dễ đọc hơn khi bạn đảm bảo rằng việc khai báo các biến này cố gắng ở gần với mức sử dụng thực tế của các biến này nhất có thể.
  • Khai báo các biến một cách bừa bãi trong phạm vi quá rộng thường là dấu hiệu của một nhà phát triển không nắm bắt được OOP hoặc cách triển khai nó. Việc nhìn thấy các biến có phạm vi quá rộng hoạt động như một lá cờ đỏ có thể có điều gì đó không ổn với phương pháp OOP (nói chung với nhà phát triển nói chung hoặc codebase nói riêng).
  • (Nhận xét của Kevin) Sử dụng người địa phương buộc bạn phải làm mọi thứ theo đúng thứ tự. Trong mã gốc (biến lớp), bạn có thể di chuyển sai passRequestToServiceClient()lên đầu phương thức và nó vẫn sẽ biên dịch. Với người dân địa phương, bạn chỉ có thể mắc lỗi đó nếu bạn đã vượt qua một biến chưa được khởi tạo, điều này đủ rõ ràng để bạn không thực sự làm điều đó.

Tuy nhiên, bất chấp những lợi ích này, tôi dường như vẫn không thể thuyết phục được nhà phát triển ban đầu rằng việc tái cấu trúc này được bảo hành, vì nó dường như mâu thuẫn với các hoạt động của chú Bob đã đề cập ở trên.

Và ngược lại, có bất kỳ nhược điểm nào đối với việc tái cấu trúc này mà tôi có thể bỏ qua không?

Vấn đề ở đây là đối số của bạn cho các biến cục bộ là hợp lệ, nhưng bạn cũng đã thực hiện các thay đổi bổ sung không chính xác và khiến bản sửa lỗi được đề xuất của bạn không thể kiểm tra mùi.

Mặc dù tôi hiểu đề xuất "không có biến lớp" của bạn và có công với nó, nhưng thực tế bạn cũng đã tự loại bỏ các phương thức đó và đó là một trò chơi bóng hoàn toàn khác. Các phương thức nên ở lại và thay vào đó bạn nên thay đổi chúng để trả về giá trị của chúng thay vì lưu trữ nó trong một biến lớp:

private byte[] getEncodedData() {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
}

private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
}

// and so on...

Tôi đồng ý với những gì bạn đã thực hiện trong processphương pháp, nhưng bạn nên gọi các phân nhóm riêng tư hơn là thực hiện trực tiếp cơ thể của chúng.

public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = getEncodedData();
    EncryptionInfo encryptionInfo = getEncryptionInfo();

    //and so on...

    return cryptoService.encryptResponse(payloadOfResponse);
}

Bạn muốn có thêm lớp trừu tượng, đặc biệt là khi bạn chạy vào các phương thức cần được sử dụng lại nhiều lần. Ngay cả khi bạn hiện không sử dụng lại các phương thức của mình , thì vẫn nên tạo ra các biểu đồ con khi có liên quan, ngay cả khi chỉ để hỗ trợ khả năng đọc mã.

Bất kể đối số biến cục bộ, tôi ngay lập tức nhận thấy rằng bản sửa lỗi được đề xuất của bạn ít đáng đọc hơn bản gốc. Tôi thừa nhận rằng việc sử dụng các biến lớp bừa bãi cũng làm mất khả năng đọc mã, nhưng không phải từ cái nhìn đầu tiên so với việc bạn đã xếp chồng tất cả logic trong một phương thức duy nhất (bây giờ dài dòng).


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

79

Mã ban đầu đang sử dụng các biến thành viên như đối số. Khi anh ta nói để giảm thiểu số lượng đối số, điều anh ta thực sự muốn nói là giảm thiểu lượng dữ liệu mà các phương thức yêu cầu để hoạt động. Đưa dữ liệu đó vào các biến thành viên sẽ không cải thiện bất cứ điều gì.


20
Hoàn toàn đồng ý! Các biến thành viên đó chỉ đơn giản là các đối số hàm ẩn. Trên thực tế, điều tồi tệ hơn kể từ bây giờ không có liên kết rõ ràng giữa giá trị của biến đó và việc sử dụng hàm (từ POV bên ngoài)
Rémi

1
Tôi muốn nói rằng đây không phải là những gì cuốn sách có nghĩa. Có bao nhiêu chức năng yêu cầu chính xác dữ liệu đầu vào bằng không để chạy? Đây là một trong những bit tôi nghĩ rằng cuốn sách đã sai.
Qwertie

1
@Qwertie Nếu bạn có một đối tượng, dữ liệu mà nó hoạt động có thể được gói gọn hoàn toàn bên trong nó. Các chức năng như process.Start();hoặc myString.ToLowerCase()không nên có vẻ quá kỳ lạ (và thực sự là dễ hiểu nhất).
R. Schmitz

5
Cả hai có một đối số: ẩn this. Người ta thậm chí có thể lập luận lập luận này được đưa ra một cách rõ ràng - trước dấu chấm.
BlackJack

47

Các câu trả lời khác đã giải thích hoàn hảo lợi ích của các biến cục bộ, vì vậy tất cả những gì còn lại là phần này của câu hỏi của bạn:

Tuy nhiên, bất chấp những lợi ích này, tôi dường như vẫn không thể thuyết phục được nhà phát triển ban đầu rằng việc tái cấu trúc này được bảo hành, vì nó dường như mâu thuẫn với các hoạt động của chú Bob đã đề cập ở trên.

Điều đó nên dễ dàng. Chỉ cần chỉ cho anh ta câu trích dẫn sau đây trong Bộ luật sạch của chú Bob:

Không có tác dụng phụ

Tác dụng phụ là dối trá. Chức năng của bạn hứa sẽ làm một việc, nhưng nó cũng làm những việc ẩn khác. Đôi khi nó sẽ tạo ra những thay đổi bất ngờ cho các biến của lớp riêng của nó. Đôi khi nó sẽ làm cho chúng đến các tham số được truyền vào hàm hoặc tới toàn cầu hệ thống. Trong cả hai trường hợp, chúng là những sai lầm sai lầm và gây tổn hại thường dẫn đến các khớp nối tạm thời kỳ lạ và phụ thuộc trật tự.

(ví dụ bỏ qua)

Tác dụng phụ này tạo ra một khớp nối tạm thời. Đó là, checkPassword chỉ có thể được gọi vào một số thời điểm nhất định (nói cách khác, khi an toàn để khởi tạo phiên). Nếu nó được gọi không đúng thứ tự, dữ liệu phiên có thể vô tình bị mất. Khớp nối tạm thời là khó hiểu, đặc biệt là khi ẩn như là một tác dụng phụ. Nếu bạn phải có khớp nối tạm thời, bạn nên làm rõ tên của hàm. Trong trường hợp này, chúng tôi có thể đổi tên hàm checkPasswordAndInitializeSession, mặc dù điều đó chắc chắn vi phạm với Do Do một điều.

Đó là, chú Bob không chỉ nói rằng một hàm nên có một vài đối số, ông cũng nói rằng các hàm nên tránh tương tác với trạng thái không cục bộ bất cứ khi nào có thể.


3
Trong một "thế giới hoàn hảo", đây sẽ là câu trả lời thứ hai được liệt kê. Câu trả lời đầu tiên cho tình huống lý tưởng mà đồng nghiệp lắng nghe lý do - nhưng nếu đồng nghiệp là một người nhiệt tình, câu trả lời này ở đây sẽ giải quyết tình huống mà không có quá nhiều khó khăn.
R. Schmitz

2
Đối với một biến thể thực tế hơn về ý tưởng này, đơn giản là lý do về trạng thái địa phương dễ dàng hơn nhiều so với trạng thái toàn cầu hoặc trạng thái toàn cầu. Các tác dụng phụ và tác dụng phụ được xác định rõ ràng và chặt chẽ hiếm khi dẫn đến các vấn đề. Ví dụ, nhiều hàm sắp xếp hoạt động tại chỗ thông qua hiệu ứng phụ, nhưng điều này dễ gây ra trong phạm vi cục bộ.
Beefster

1
Ah, cái cũ tốt "trong một tiên đề mâu thuẫn, có thể chứng minh bất cứ điều gì". Vì không có sự thật phũ phàng và IRL dối trá, bất kỳ giáo điều nào cũng sẽ phải bao gồm các tuyên bố nêu lên những điều trái ngược tức là mâu thuẫn.
ivan_pozdeev

26

"Nó mâu thuẫn với những gì chú của ai đó nghĩ" KHÔNG BAO GIỜ là một cuộc tranh luận tốt. KHÔNG BAO GIỜ. Đừng lấy sự khôn ngoan từ các chú, hãy nghĩ cho chính mình.

Điều đó nói rằng, các biến thể hiện nên được sử dụng để lưu trữ thông tin thực sự cần thiết để được lưu trữ vĩnh viễn hoặc bán vĩnh viễn. Thông tin ở đây không phải là. Nó rất đơn giản để sống mà không có các biến thể hiện, vì vậy chúng có thể đi.

Kiểm tra: Viết nhận xét tài liệu cho từng biến thể hiện. Bạn có thể viết bất cứ điều gì không hoàn toàn vô nghĩa? Và viết bình luận tài liệu cho bốn người truy cập. Họ là vô nghĩa như nhau.

Tệ nhất là, giả sử cách giải mã các thay đổi, bởi vì bạn sử dụng một dịch vụ tiền điện tử khác. Thay vì phải thay đổi bốn dòng mã, bạn phải thay thế bốn biến đối tượng bằng các biến khác nhau, bốn getters bằng các biến khác nhau và thay đổi bốn dòng mã.

Nhưng tất nhiên phiên bản đầu tiên là thích hợp hơn nếu bạn được trả theo dòng mã. 31 dòng thay vì 11 dòng. Thêm ba lần dòng để viết và để duy trì mãi mãi, để đọc khi bạn gỡ lỗi một cái gì đó, để thích ứng khi cần thay đổi, để nhân đôi nếu bạn hỗ trợ dịch vụ mật mã thứ hai.

(Bỏ lỡ điểm quan trọng là việc sử dụng các biến cục bộ buộc bạn phải thực hiện các cuộc gọi theo đúng thứ tự).


16
Suy nghĩ cho bản thân tất nhiên là tốt. Nhưng đoạn mở đầu của bạn có hiệu quả bao gồm người học hoặc đàn em bỏ qua đầu vào của giáo viên hoặc người cao niên; mà đi quá xa
Flater

9
@Flater Sau khi suy nghĩ về đầu vào của giáo viên hoặc người cao niên và thấy họ sai, loại bỏ đầu vào của họ là điều đúng đắn duy nhất. Cuối cùng, nó không phải là về việc bác bỏ, mà là về việc đặt câu hỏi và chỉ loại bỏ nó nếu nó chắc chắn chứng minh là sai.
glglgl

10
@ChristianHackl: Tôi hoàn toàn ở trên tàu với việc không mù quáng theo chủ nghĩa truyền thống, nhưng tôi cũng sẽ không phủ nhận điều đó một cách mù quáng. Câu trả lời dường như gợi ý cho eschew có được kiến ​​thức có lợi cho quan điểm của riêng bạn, và đó không phải là một cách tiếp cận lành mạnh. Mù quáng làm theo ý kiến ​​của người khác, không. Đặt câu hỏi gì đó khi bạn không đồng ý, rõ ràng là có. Hoàn toàn bác bỏ nó vì bạn không đồng ý, không. Khi tôi đọc nó, đoạn đầu tiên của câu trả lời ít nhất dường như gợi ý cái sau. Nó phụ thuộc rất nhiều vào ý nghĩa của gnasher với "nghĩ cho chính mình", thứ cần được xây dựng.
Flater

9
Trên nền tảng chia sẻ trí tuệ, điều này có vẻ hơi lạc
lõng

4
KHÔNG BAO GIỜ cũng KHÔNG BAO GIỜ là một cuộc tranh luận tốt ... Tôi hoàn toàn đồng ý rằng các quy tắc tồn tại để hướng dẫn, không quy định. Tuy nhiên, các quy tắc về quy tắc chỉ là một lớp con của quy tắc, vì vậy bạn đã nói phán quyết của riêng mình ...
cmaster

14

Mục tiêu, cơ sở khoa học để ủng hộ các biến cục bộ hơn các biến thể hiện là gì? Tôi dường như không thể đặt ngón tay của mình lên nó. Trực giác của tôi nói với tôi rằng các khớp nối ẩn là xấu và phạm vi hẹp tốt hơn phạm vi rộng. Nhưng khoa học để sao lưu này là gì?

Các biến sơ thẩm là để biểu diễn các thuộc tính của đối tượng máy chủ của chúng, không phải để biểu diễn các thuộc tính cụ thể cho các luồng tính toán có phạm vi hẹp hơn so với chính đối tượng. Một số lý do để vẽ một sự khác biệt như vậy dường như chưa được đề cập xoay quanh vấn đề đồng thời và tái định cư. Nếu các phương thức trao đổi dữ liệu bằng cách đặt các giá trị của các biến thể hiện, thì hai luồng đồng thời có thể dễ dàng ghi đè các giá trị của nhau cho các biến cá thể đó, mang lại các lỗi không liên tục, khó tìm.

Ngay cả một luồng chỉ có thể gặp phải các vấn đề dọc theo các dòng đó, bởi vì có nguy cơ cao rằng một mẫu trao đổi dữ liệu dựa trên các biến thể hiện làm cho các phương thức không được gửi lại. Tương tự, nếu các biến giống nhau được sử dụng để truyền dữ liệu giữa các cặp phương thức khác nhau, thì có một rủi ro là một luồng thực thi ngay cả một chuỗi các lệnh gọi phương thức không đệ quy sẽ gặp các lỗi xoay quanh các sửa đổi bất ngờ của các biến thể hiện có liên quan.

Để có được kết quả chính xác một cách đáng tin cậy trong kịch bản như vậy, bạn cần sử dụng các biến riêng biệt để liên lạc giữa từng cặp phương thức trong đó phương thức này gọi phương thức kia hoặc phương thức khác để thực hiện tất cả các chi tiết triển khai của tất cả các phương thức khác phương thức nó gọi, dù trực tiếp hay gián tiếp. Điều này là dễ vỡ, và nó quy mô kém.


7
Cho đến nay, đây là câu trả lời duy nhất đề cập đến an toàn luồng và đồng thời. Điều đó thật tuyệt vời khi đưa ra ví dụ mã cụ thể trong câu hỏi: một phiên bản của someBusinessProcess không thể xử lý một cách an toàn nhiều yêu cầu được mã hóa cùng một lúc. Phương thức public EncryptedResponse process(EncryptedRequest encryptedRequest)này không được đồng bộ hóa và các cuộc gọi đồng thời hoàn toàn có thể làm tắc nghẽn các giá trị của các biến thể hiện. Đây là một điểm tốt để đưa lên.
Joshua Taylor

9

Thảo luận chỉ process(...), ví dụ đồng nghiệp của bạn dễ đọc hơn theo nghĩa logic kinh doanh. Ngược lại, ví dụ truy cập của bạn mất nhiều hơn một cái nhìn lướt qua để rút ra bất kỳ ý nghĩa nào.

Điều đó đang được nói, mã sạch là cả dễ đọc và chất lượng tốt - đẩy trạng thái địa phương ra một không gian toàn cầu hơn chỉ là lắp ráp cấp cao, vì vậy không có chất lượng.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest request) {
    checkNotNull(encryptedRequest);

    return encryptResponse
      (routeTo
         ( destination()
         , requestData(request)
         , destinationEncryption()
         )
      );
  }

  private byte[] requestData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo destinationEncryption() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI destination() {
    return router.getDestination().getUri();
  }

  private EncryptedObject routeTo(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }

  private void encryptResponse(EncryptedObject payloadOfResponse) {
    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

Đây là một biểu hiện loại bỏ sự cần thiết của các biến ở bất kỳ phạm vi nào. Có trình biên dịch sẽ tạo ra chúng nhưng phần quan trọng là nó kiểm soát điều đó để mã sẽ hiệu quả. Trong khi cũng tương đối dễ đọc.

Chỉ là một điểm về đặt tên. Bạn muốn tên ngắn nhất có ý nghĩa và mở rộng thông tin đã có sẵn. I E. DestinationURI, 'URI' đã được biết đến bằng chữ ký loại.


4
Loại bỏ tất cả các biến không nhất thiết làm cho mã dễ đọc hơn.
Pharap

Loại bỏ hoàn toàn tất cả các biến với phong cách pointfree en.wikipedia.org/wiki/Tacit_programming
Marcin

@Pharap Đúng, thiếu biến không đảm bảo mức độ dễ đọc. Trong một số trường hợp, nó thậm chí còn làm cho việc gỡ lỗi trở nên khó khăn hơn. Vấn đề là các tên được chọn tốt, cách sử dụng biểu thức rõ ràng, có thể truyền đạt một ý tưởng rất rõ ràng, trong khi vẫn hiệu quả.
Kain0_0

7

Tôi sẽ chỉ loại bỏ các biến và phương thức riêng tư hoàn toàn. Đây là cấu trúc lại của tôi:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    return cryptoService.encryptResponse(
        serviceClient.handle(router.getDestination().getUri(),
        cryptoService.decryptRequest(encryptedRequest, byte[].class),
        cryptoService.getEncryptionInfoForDefaultClient()));
  }
}

Đối với phương thức riêng tư, ví dụ router.getDestination().getUri()rõ ràng và dễ đọc hơn getDestinationURI(). Tôi thậm chí sẽ chỉ lặp lại rằng nếu tôi sử dụng cùng một dòng hai lần trong cùng một lớp. Nhìn theo một cách khác, nếu có nhu cầu về a getDestinationURI(), thì có lẽ nó thuộc về một số lớp khác, không phải trong SomeBusinessProcesslớp.

Đối với các biến và thuộc tính, nhu cầu chung cho chúng là giữ các giá trị được sử dụng sau này. Nếu lớp không có giao diện chung cho các thuộc tính, có lẽ chúng không nên là thuộc tính. Loại thuộc tính lớp tồi tệ nhất được sử dụng có lẽ là để truyền các giá trị giữa các phương thức riêng tư bằng cách tác dụng phụ.

Dù sao, lớp chỉ cần làm process()và sau đó đối tượng sẽ bị ném đi, không cần phải giữ bất kỳ trạng thái nào trong bộ nhớ. Tiềm năng tái cấu trúc hơn nữa sẽ là loại bỏ CryptoService ra khỏi lớp đó.

Dựa trên ý kiến, tôi muốn thêm câu trả lời này dựa trên thực tiễn thế giới thực. Thật vậy, trong đánh giá mã, điều đầu tiên tôi chọn là cấu trúc lại lớp và loại bỏ công việc mã hóa / giải mã. Khi đã xong, tôi sẽ hỏi xem các phương thức và biến có cần thiết không, chúng có được đặt tên chính xác hay không. Mã cuối cùng có thể sẽ gần hơn với điều này:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;

  public Response process(Request request) {
    return serviceClient.handle(router.getDestination().getUri());
  }
}

Với mã trên, tôi không nghĩ rằng nó cần tái cấu trúc thêm. Theo quy định, tôi nghĩ cần có kinh nghiệm để biết khi nào và khi nào không áp dụng chúng. Các quy tắc không phải là lý thuyết được chứng minh là có hiệu quả trong mọi tình huống.

Mặt khác, việc xem xét mã có tác động thực sự đến thời gian trước khi một đoạn mã có thể vượt qua. Thủ thuật của tôi là có ít mã hơn và làm cho nó dễ hiểu. Một tên biến có thể là một điểm thảo luận, nếu tôi có thể loại bỏ nó, người đánh giá thậm chí sẽ không cần phải suy nghĩ về nó.


Upvote của tôi, mặc dù nhiều người sẽ do dự ở đây. Tất nhiên một số trừu tượng, có ý nghĩa. (BTW một phương pháp gọi là "quy trình" là khủng khiếp.) Nhưng ở đây logic là tối thiểu. Tuy nhiên, câu hỏi của OP là về toàn bộ kiểu mã, và trường hợp này có thể phức tạp hơn.
Eggen

1
Một vấn đề rõ ràng với việc kết nối tất cả vào một cuộc gọi phương thức là khả năng đọc tuyệt đối. Nó cũng không hoạt động nếu bạn cần nhiều hơn một thao tác trên một đối tượng nhất định. Ngoài ra, điều này không thể gỡ lỗi vì bạn không thể thực hiện các thao tác và kiểm tra các đối tượng. Mặc dù điều này hoạt động ở cấp độ kỹ thuật, tôi sẽ không ủng hộ điều này vì nó bỏ bê ồ ạt các khía cạnh không chạy trong phát triển phần mềm.
Flater

@Flater Tôi đồng ý với ý kiến ​​của bạn, chúng tôi không muốn áp dụng điều này ở mọi nơi. Tôi chỉnh sửa câu trả lời của tôi để làm rõ vị trí thực tế của tôi. Điều tôi muốn chỉ ra là trong thực tế, chúng tôi chỉ áp dụng các quy tắc khi nó phù hợp. Trong trường hợp này, cuộc gọi phương thức chuỗi là tốt, và nếu tôi cần gỡ lỗi, tôi sẽ gọi các thử nghiệm cho các phương thức chuỗi.
imel96

@JoopEggen Vâng, trừu tượng có ý nghĩa. Trong ví dụ này, các phương thức riêng tư không đưa ra bất kỳ sự trừu tượng nào, người dùng của lớp thậm chí không biết về chúng
imel96

1
@ imel96 thật buồn cười khi có lẽ bạn là một trong số ít người ở đây nhận thấy rằng sự kết hợp giữa ServiceClient và CryptoService khiến việc tập trung vào việc tiêm CS vào SC thay vì vào SBP, giải quyết vấn đề tiềm ẩn ở cấp độ kiến ​​trúc cao hơn ... đó là IMVHO điểm chính của câu chuyện này; quá dễ dàng để theo dõi bức tranh lớn trong khi tập trung vào các chi tiết.
vaxquis

4

Câu trả lời của Flater bao gồm các vấn đề về phạm vi khá tốt, nhưng tôi nghĩ rằng cũng có một vấn đề khác ở đây.

Lưu ý rằng có một sự khác biệt giữa một chức năng xử lý dữ liệu và một chức năng chỉ đơn giản là truy cập dữ liệu .

Cái trước thực thi logic kinh doanh thực tế, trong khi cái sau tiết kiệm việc gõ và có lẽ thêm an toàn bằng cách thêm một giao diện đơn giản và dễ sử dụng hơn.

Trong trường hợp này, có vẻ như các chức năng truy cập dữ liệu không lưu gõ và không được sử dụng lại ở bất cứ đâu (hoặc sẽ có các vấn đề khác trong việc xóa chúng). Vì vậy, các chức năng này không nên tồn tại.

Bằng cách chỉ giữ logic kinh doanh trong các chức năng được đặt tên, chúng tôi có được cả hai thế giới tốt nhất (ở đâu đó giữa câu trả lời của Flatercâu trả lời của imel96 ):

public EncryptedResponse process(EncryptedRequest encryptedRequest) {

    byte[] requestData = decryptRequest(encryptedRequest);
    EncryptedObject responseData = handleRequest(router.getDestination().getUri(), requestData, cryptoService.getEncryptionInfoForDefaultClient());
    EncryptedResponse response = encryptResponse(responseData);

    return response;
}

// define: decryptRequest(), handleRequest(), encryptResponse()

3

Điều đầu tiên và quan trọng nhất: Đôi khi chú Bob dường như giống như một nhà thuyết giáo, nhưng nói rằng có những ngoại lệ đối với các quy tắc của ông.

Toàn bộ ý tưởng của Clean Code là cải thiện khả năng đọc và tránh lỗi. Có một số quy tắc đang vi phạm lẫn nhau.

Lập luận của ông về các chức năng là các hàm niladic là tốt nhất, tuy nhiên có thể chấp nhận tối đa ba tham số. Cá nhân tôi nghĩ rằng 4 cũng ok.

Khi các biến thể hiện được sử dụng, chúng sẽ tạo một lớp kết hợp. Điều đó có nghĩa là, các biến nên được sử dụng trong nhiều, nếu không phải tất cả các phương thức không tĩnh.

Các biến không được sử dụng ở nhiều nơi trong lớp, nên được di chuyển.

Tôi sẽ không xem xét phiên bản gốc cũng như phiên bản được cấu trúc lại là tối ưu và @Flater đã nêu rất rõ những gì có thể được thực hiện với các giá trị trả về. Nó cải thiện khả năng đọc và giảm lỗi để sử dụng các giá trị trả về.


1

Do đó, các biến cục bộ giảm phạm vi do đó giới hạn các cách mà các biến có thể được sử dụng và do đó giúp ngăn ngừa các lớp lỗi nhất định và cải thiện khả năng đọc.

Biến sơ thẩm giảm các cách thức mà hàm có thể được gọi cũng giúp giảm các lớp lỗi nhất định và cải thiện khả năng đọc.

Nói một điều là đúng và người kia sai cũng có thể là một kết luận hợp lệ trong bất kỳ trường hợp cụ thể nào, nhưng như lời khuyên chung ...

TL; DR: Tôi nghĩ lý do bạn ngửi thấy quá nhiều nhiệt tình là vì quá nhiệt tình.


0

Mặc dù thực tế là các phương thức bắt đầu bằng get ... không nên trả về khoảng trống, việc phân tách các mức trừu tượng trong các phương thức được đưa ra trong giải pháp đầu tiên. Mặc dù giải pháp thứ hai có phạm vi rộng hơn nhưng vẫn khó để suy luận về những gì đang diễn ra trong phương pháp. Việc gán các biến cục bộ không cần thiết ở đây. Tôi sẽ giữ các tên phương thức và cấu trúc lại mã thành một cái gì đó như thế:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    return getEncryptedResponse(
            passRequestToServiceClient(getDestinationURI(), getEncodedData(encryptedRequest) getEncryptionInfo())
        );
  }

  private EncryptedResponse getEncryptedResponse(EncryptedObject encryptedObject) {
    return cryptoService.encryptResponse(encryptedObject);
  }

  private byte[] getEncodedData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI getDestinationURI() {
    return router.getDestination().getUri();
  }

  private EncryptedObject passRequestToServiceClient(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

0

Cả hai điều này đều giống nhau và sự khác biệt về hiệu suất là không đáng chú ý, vì vậy tôi không nghĩ có một lập luận khoa học . Nó đi xuống sở thích chủ quan sau đó.

Và tôi cũng có xu hướng thích cách của bạn tốt hơn so với đồng nghiệp của bạn. Tại sao? Bởi vì tôi nghĩ nó dễ đọc và dễ hiểu hơn, bất chấp những gì tác giả sách nói.

Cả hai cách đều hoàn thành cùng một điều, nhưng cách của anh ta trải rộng hơn. Để đọc mã đó, bạn cần thay đổi qua lại giữa một số hàm và biến thành viên. Đó không phải là tất cả cô đọng ở một nơi, bạn cần nhớ tất cả những gì trong đầu để hiểu nó. Đó là một tải nhận thức lớn hơn nhiều.

Ngược lại, cách tiếp cận của bạn đóng gói mọi thứ dày đặc hơn nhiều, nhưng không để làm cho nó không thể xuyên thủng. Bạn chỉ cần đọc từng dòng và bạn không cần phải ghi nhớ quá nhiều để hiểu nó.

Tuy nhiên, nếu anh ấy thường viết mã theo cách như vậy, tôi có thể tưởng tượng rằng đối với anh ấy, nó có thể là cách khác.


Điều đó thực sự đã được chứng minh là một trở ngại lớn cho tôi để hiểu dòng chảy. Ví dụ này khá nhỏ, nhưng một ví dụ khác đã sử dụng các biến đối tượng 35 (!) Cho các kết quả trung gian của nó, không tính hàng tá biến đối tượng có chứa các phụ thuộc. Nó khá khó để theo dõi, vì bạn phải theo dõi những gì đã được thiết lập trước đó. Một số thậm chí đã được tái sử dụng sau này, làm cho nó thậm chí còn khó hơn. May mắn thay, các lập luận được trình bày ở đây cuối cùng đã thuyết phục được đồng nghiệp của tôi đồng ý với tái cấu trúc.
Alexander
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.