Các đồng nghiệp của tôi muốn nói "ghi nhật ký / lưu trữ / v.v. là mối quan tâm xuyên suốt" và sau đó tiến hành sử dụng đơn lẻ tương ứng ở mọi nơi. Tuy nhiên, họ yêu thích IoC và DI.
Có thực sự là một cái cớ hợp lệ để phá vỡ nguyên tắc SOLI D ?
Các đồng nghiệp của tôi muốn nói "ghi nhật ký / lưu trữ / v.v. là mối quan tâm xuyên suốt" và sau đó tiến hành sử dụng đơn lẻ tương ứng ở mọi nơi. Tuy nhiên, họ yêu thích IoC và DI.
Có thực sự là một cái cớ hợp lệ để phá vỡ nguyên tắc SOLI D ?
Câu trả lời:
Không.
RẮN tồn tại như hướng dẫn để giải thích cho sự thay đổi không thể tránh khỏi. Bạn có thực sự sẽ không bao giờ thay đổi thư viện đăng nhập, hoặc nhắm mục tiêu, hoặc lọc hoặc định dạng hoặc ...? Bạn có thực sự sẽ không thay đổi thư viện bộ nhớ đệm, hoặc mục tiêu, hoặc chiến lược, hoặc phạm vi, hoặc ...?
Tất nhiên rồi. Ít nhất , bạn sẽ muốn chế giễu những điều này một cách lành mạnh để cô lập chúng để thử nghiệm. Và nếu bạn muốn cô lập chúng để thử nghiệm, có khả năng bạn sẽ gặp phải một lý do kinh doanh nơi bạn muốn cô lập chúng vì lý do thực tế.
Và sau đó bạn sẽ nhận được lập luận rằng chính logger sẽ xử lý thay đổi. "Ồ, nếu mục tiêu / lọc / định dạng / chiến lược thay đổi, thì chúng ta sẽ thay đổi cấu hình!" Đó là rác. Giờ đây, bạn không chỉ có một Đối tượng Thần xử lý tất cả những điều này, mà bạn còn viết mã bằng XML (hoặc tương tự) khi bạn không nhận được phân tích tĩnh, bạn không bị lỗi thời gian biên dịch và bạn không ' t thực sự có được bài kiểm tra đơn vị hiệu quả.
Có trường hợp nào vi phạm các hướng dẫn RẮN? Chắc chắn rồi. Đôi khi mọi thứ sẽ không thay đổi (mà không yêu cầu viết lại hoàn toàn dù sao). Đôi khi một vi phạm nhẹ của LSP là giải pháp sạch nhất. Đôi khi làm một giao diện bị cô lập không cung cấp giá trị.
Nhưng ghi nhật ký và lưu trữ (và các mối quan tâm xuyên suốt phổ biến khác) không phải là những trường hợp đó. Chúng thường là những ví dụ tuyệt vời về các vấn đề về khớp nối và thiết kế mà bạn gặp phải khi bạn bỏ qua các hướng dẫn.
String
hoặc Int32
hoặc thậm chí List
ra khỏi mô-đun của bạn. Chỉ có một mức độ hợp lý và lành mạnh để lên kế hoạch thay đổi. Và, ngoài các loại "cốt lõi" rõ ràng nhất, nhận ra những gì bạn có thể thay đổi thực sự chỉ là vấn đề kinh nghiệm và phán đoán.
Đúng
Đây là toàn bộ quan điểm của thuật ngữ "mối quan tâm xuyên suốt" - nó có nghĩa là một cái gì đó không phù hợp gọn gàng trong nguyên tắc RẮN.
Đây là nơi chủ nghĩa duy tâm gặp gỡ với thực tế.
Những người bán mới cho RẮN và xuyên suốt thường gặp phải thử thách tinh thần này. Không sao đâu, đừng hoảng sợ. Cố gắng đặt mọi thứ vào điều khoản của RẮN, nhưng có một vài nơi như ghi nhật ký và lưu vào bộ đệm trong đó RẮN chỉ không có ý nghĩa. Cắt ngang là anh em của RẮN, họ song hành cùng nhau.
HttpContextBase
(được giới thiệu chính xác vì lý do này). Tôi biết chắc chắn rằng thực tế của tôi sẽ thực sự chua chát nếu không có lớp học này.
Để đăng nhập tôi nghĩ rằng nó là. Ghi nhật ký là phổ biến và thường không liên quan đến chức năng dịch vụ. Đó là phổ biến và được hiểu rõ để sử dụng các mẫu singleton khung đăng nhập. Nếu bạn không, bạn đang tạo và tiêm logger ở mọi nơi và bạn không muốn điều đó.
Một vấn đề ở trên là ai đó sẽ nói 'nhưng làm cách nào tôi có thể kiểm tra đăng nhập?' . Suy nghĩ của tôi là tôi thường không kiểm tra việc ghi nhật ký, ngoài việc khẳng định rằng tôi thực sự có thể đọc các tệp nhật ký và hiểu chúng. Khi tôi thấy đăng nhập được kiểm tra, thường là do ai đó cần phải xác nhận rằng một lớp đã thực sự làm điều gì đó và họ đang sử dụng thông điệp tường trình để nhận phản hồi đó. Tôi thà đăng ký một số người nghe / người quan sát vào lớp đó và khẳng định trong các bài kiểm tra của tôi được gọi. Sau đó, bạn có thể đặt nhật ký sự kiện của mình trong người quan sát đó.
Tôi nghĩ rằng bộ nhớ đệm là một kịch bản hoàn toàn khác nhau, tuy nhiên.
2 xu của tôi ...
Có và không.
Bạn không bao giờ nên thực sự vi phạm các nguyên tắc bạn áp dụng; nhưng, các nguyên tắc của bạn phải luôn luôn mang sắc thái và được áp dụng để phục vụ cho mục tiêu cao hơn. Vì vậy, với sự hiểu biết có điều kiện đúng đắn, một số vi phạm rõ ràng có thể không phải là vi phạm thực sự đối với "tinh thần" hay "cơ thể của các nguyên tắc nói chung".
Các nguyên tắc RẮN nói riêng, ngoài việc đòi hỏi nhiều sắc thái, cuối cùng còn phụ thuộc vào mục tiêu "cung cấp phần mềm làm việc, có thể bảo trì". Vì vậy, việc tuân thủ bất kỳ nguyên tắc RẮN cụ thể nào là tự đánh bại và tự mâu thuẫn khi làm như vậy mâu thuẫn với các mục tiêu của RẮN. Và ở đây, tôi thường lưu ý rằng việc cung cấp khả năng duy trì trumps .
Vì vậy, những gì về D trong RẮN ? Chà, nó góp phần tăng khả năng bảo trì bằng cách làm cho mô-đun tái sử dụng của bạn tương đối không tin vào bối cảnh của nó. Và chúng ta có thể định nghĩa "mô-đun có thể tái sử dụng" là "một tập hợp mã bạn dự định sử dụng trong một bối cảnh riêng biệt khác." Và điều đó áp dụng cho một chức năng, lớp, tập hợp lớp và chương trình.
Và vâng, việc thay đổi triển khai logger có thể đặt mô-đun của bạn vào một "bối cảnh khác biệt khác".
Vì vậy, hãy để tôi cung cấp hai cảnh báo lớn của tôi :
Thứ nhất: Vẽ các dòng xung quanh các khối mã tạo thành "mô-đun có thể tái sử dụng" là một vấn đề của sự đánh giá chuyên nghiệp. Và phán đoán của bạn nhất thiết phải giới hạn trong kinh nghiệm của bạn.
Nếu bạn không hiện kế hoạch sử dụng một mô-đun trong bối cảnh khác, đó là lẽ OK cho nó phụ thuộc vô vọng vào nó. Thông báo trước để cảnh báo: Kế hoạch của bạn có thể sai - nhưng điều đó cũng ổn. Bạn viết mô-đun sau mô-đun càng lâu, ý thức của bạn sẽ càng trực quan và chính xác hơn về việc "tôi sẽ cần điều này một lần nữa vào một ngày nào đó". Nhưng, có lẽ bạn sẽ không bao giờ có thể hồi tưởng lại, "Tôi đã mô đun hóa và tách rời mọi thứ đến mức lớn nhất có thể, nhưng không thừa ."
Nếu bạn cảm thấy có lỗi về phán đoán của mình, hãy đi xưng tội và tiếp tục ...
Thứ hai: Kiểm soát đảo ngược không bằng phụ thuộc tiêm .
Điều này đặc biệt đúng khi bạn bắt đầu tiêm phụ thuộc vào quảng cáo . Dependency Injection là một chiến thuật hữu ích cho chiến lược bao quát IoC. Nhưng, tôi cho rằng DI có hiệu quả kém hơn so với một số chiến thuật khác - như sử dụng giao diện và bộ điều hợp - các điểm tiếp xúc duy nhất với bối cảnh từ bên trong mô-đun.
Và hãy thực sự tập trung vào điều này trong một giây. Bởi vì, ngay cả khi bạn tiêm một Logger
nauseam quảng cáo , bạn cần phải viết mã trên Logger
giao diện. Bạn không thể bắt đầu sử dụng mới Logger
từ một nhà cung cấp khác có các tham số theo thứ tự khác. Khả năng đó đến từ mã hóa, trong mô-đun, đối với một giao diện tồn tại trong mô-đun và có một mô hình con (Adaptor) duy nhất trong đó để quản lý sự phụ thuộc.
Và nếu bạn đang mã hóa bộ điều hợp, cho dù bộ Logger
điều hợp đó được đưa vào Bộ điều hợp đó hay Bộ điều hợp được phát hiện nói chung là khá đáng kể đối với mục tiêu bảo trì tổng thể. Và quan trọng hơn, nếu bạn có Bộ điều hợp cấp mô-đun, có lẽ thật vô lý khi tiêm nó vào bất cứ thứ gì. Nó được viết cho mô-đun.
tl; dr - Đừng lo lắng về các nguyên tắc mà không xem xét lý do tại sao bạn sử dụng các nguyên tắc. Và, thực tế hơn, chỉ cần xây dựng một Adapter
cho mỗi mô-đun. Sử dụng phán đoán của bạn khi quyết định nơi bạn vẽ ranh giới "mô-đun". Từ trong mỗi mô-đun, hãy tiếp tục và tham khảo trực tiếp đến Adapter
. Và chắc chắn, tiêm logger thực sự vào Adapter
- nhưng không phải vào mọi thứ nhỏ có thể cần nó.
Ý tưởng rằng việc đăng nhập phải luôn luôn được thực hiện như một người độc thân là một trong những lời nói dối đã được nói rất thường xuyên, nó đã đạt được lực kéo.
Từ lâu, các hệ điều hành hiện đại đã được công nhận rằng bạn có thể muốn đăng nhập vào nhiều nơi tùy thuộc vào bản chất của đầu ra .
Các nhà thiết kế hệ thống nên liên tục đặt câu hỏi về hiệu quả của các giải pháp trong quá khứ trước khi mù quáng đưa chúng vào những giải pháp mới. Nếu họ không thực hiện sự siêng năng như vậy, thì họ sẽ không làm việc của họ.
Đăng nhập thực sự là một trường hợp đặc biệt.
@Telastyn viết:
Bạn có thực sự sẽ không bao giờ thay đổi thư viện đăng nhập, hoặc nhắm mục tiêu, hoặc lọc hoặc định dạng hoặc ...?
Nếu bạn dự đoán rằng bạn có thể cần thay đổi thư viện ghi nhật ký của mình, thì bạn nên sử dụng mặt tiền; tức là SLF4J nếu bạn đang ở trong thế giới Java.
Đối với phần còn lại, một thư viện ghi nhật ký tốt sẽ thay đổi việc ghi nhật ký, sự kiện nào được lọc, cách các sự kiện nhật ký được định dạng bằng các tệp cấu hình logger và (nếu cần) các lớp plugin tùy chỉnh. Có một số lựa chọn thay thế ngoài kệ.
Nói tóm lại, đây là những vấn đề được giải quyết ... để đăng nhập ... và do đó không cần phải sử dụng Dependency Injection để giải quyết chúng.
Trường hợp duy nhất mà DI có thể có lợi (trên các phương pháp ghi nhật ký tiêu chuẩn) là nếu bạn muốn ghi nhật ký ứng dụng của mình vào kiểm tra đơn vị. Tuy nhiên, tôi nghi ngờ hầu hết các nhà phát triển sẽ nói rằng việc đăng nhập không phải là một phần của chức năng lớp và không phải là thứ cần được kiểm tra.
@Telastyn viết:
Và sau đó bạn sẽ nhận được lập luận rằng chính logger sẽ xử lý thay đổi. "Ồ, nếu mục tiêu / lọc / định dạng / chiến lược thay đổi, thì chúng ta sẽ thay đổi cấu hình!" Đó là rác. Giờ đây, bạn không chỉ có một Đối tượng Thần xử lý tất cả những điều này, mà bạn còn viết mã bằng XML (hoặc tương tự) khi bạn không nhận được phân tích tĩnh, bạn không bị lỗi thời gian biên dịch và bạn không ' t thực sự có được bài kiểm tra đơn vị hiệu quả.
Tôi sợ rằng đó là một ripost rất lý thuyết. Trong thực tế, hầu hết các nhà phát triển và nhà tích hợp hệ thống THÍCH thực tế là bạn có thể định cấu hình ghi nhật ký thông qua tệp cấu hình. Và họ THÍCH thực tế là họ không mong đợi đơn vị kiểm tra ghi nhật ký của mô-đun.
Chắc chắn, nếu bạn xử lý các cấu hình ghi nhật ký thì bạn có thể gặp sự cố, nhưng chúng sẽ biểu hiện là ứng dụng bị lỗi trong khi khởi động hoặc đăng nhập quá nhiều / quá ít. 1) Những sự cố này có thể dễ dàng khắc phục bằng cách sửa lỗi trong tệp cấu hình. 2) Thay thế là một chu trình xây dựng / phân tích / kiểm tra / triển khai hoàn chỉnh mỗi khi bạn thay đổi mức ghi nhật ký. Điều đó không được chấp nhận.
Có và Không !
Có: Tôi nghĩ thật hợp lý khi các hệ thống con khác nhau (hoặc các lớp hoặc thư viện ngữ nghĩa hoặc các khái niệm khác của gói mô-đun) mỗi chấp nhận (cùng hoặc) có khả năng ghi nhật ký khác nhau trong quá trình khởi tạo thay vì tất cả các hệ thống con đều sử dụng cùng một đơn chung .
Tuy nhiên,
Không: đồng thời không hợp lý khi tham số hóa việc ghi nhật ký trong mọi đối tượng nhỏ (theo phương thức khởi tạo hoặc phương thức cá thể). Để tránh sự phình to không cần thiết và vô nghĩa, các thực thể nhỏ hơn nên sử dụng bộ ghi đơn lẻ trong bối cảnh kèm theo của chúng.
Đây là một lý do trong số nhiều người khác nghĩ về tính mô đun theo các mức: các phương thức được gói vào các lớp, trong khi các lớp được gói vào các hệ thống con và / hoặc các lớp ngữ nghĩa. Những bó lớn hơn là những công cụ trừu tượng có giá trị; chúng ta nên đưa ra những cân nhắc khác nhau trong các ranh giới mô-đun hơn là khi vượt qua chúng.
Đầu tiên, nó bắt đầu với bộ đệm đơn singleton, những thứ tiếp theo bạn thấy là các singleton mạnh cho lớp cơ sở dữ liệu giới thiệu trạng thái toàn cầu, API không mô tả của class
es và mã không thể kiểm chứng.
Nếu bạn quyết định không có một singleton cho cơ sở dữ liệu, thì có lẽ không nên có một singleton cho bộ đệm, sau tất cả, chúng đại diện cho một khái niệm rất giống nhau, lưu trữ dữ liệu, chỉ sử dụng các cơ chế khác nhau.
Sử dụng một singleton trong một lớp sẽ biến một lớp có một số lượng phụ thuộc cụ thể thành một lớp có, theo lý thuyết , một số lượng vô hạn của chúng, bởi vì bạn không bao giờ biết điều gì thực sự ẩn đằng sau phương thức tĩnh.
Trong thập kỷ qua tôi đã dành lập trình, chỉ có một trường hợp tôi chứng kiến nỗ lực thay đổi logic ghi nhật ký (sau đó được viết là singleton). Vì vậy, mặc dù tôi thích tiêm phụ thuộc, đăng nhập thực sự không phải là một mối quan tâm lớn như vậy. Cache, mặt khác, tôi chắc chắn sẽ luôn luôn làm như một phụ thuộc.
Có và Không, nhưng chủ yếu là Không
Tôi giả sử hầu hết các cuộc hội thoại dựa trên trường hợp tĩnh so với tiêm. Không ai đề xuất rằng đăng nhập phá vỡ SRP tôi giả sử? Chúng tôi chủ yếu nói về "nguyên tắc đảo ngược phụ thuộc". Tôi không đồng ý với câu trả lời của Telastyn.
Khi nào thì sử dụng statics? Bởi vì rõ ràng đôi khi nó ổn. Câu trả lời đúng có về lợi ích của sự trừu tượng và câu trả lời "không" chỉ ra rằng chúng là thứ bạn phải trả cho. Một trong những lý do khiến công việc của bạn khó khăn là không có một câu trả lời nào bạn có thể viết ra và áp dụng cho tất cả các tình huống.
Lấy:
Convert.ToInt32("1")
Tôi thích điều này hơn:
private readonly IConverter _converter;
public MyClass(IConverter converter)
{
Guard.NotNull(converter)
_converter = conveter
}
....
var foo = _converter.ToInt32("1");
Tại sao? Tôi chấp nhận rằng tôi sẽ cần cấu trúc lại mã nếu tôi cần sự linh hoạt để trao đổi mã chuyển đổi. Tôi chấp nhận rằng tôi sẽ không thể chế nhạo điều này. Tôi tin rằng sự đơn giản và căng thẳng là giá trị thương mại này.
Nhìn vào đầu kia của quang phổ, nếu IConverter
là một SqlConnection
, tôi sẽ khá kinh hoàng khi thấy đó là một cuộc gọi tĩnh. Những lý do tại sao là rõ ràng hàng ngày. Tôi đã chỉ ra rằng một từ SQLConnection
có thể khá "xuyên suốt" trong một tràng pháo tay, vì vậy tôi sẽ không sử dụng những từ chính xác đó.
Là đăng nhập giống như một SQLConnection
hoặc Convert.ToInt32
? Tôi muốn nói nhiều hơn như 'SQLConnection`.
Bạn nên chế giễu Đăng nhập . Nó nói chuyện với thế giới bên ngoài. Khi viết một phương thức bằng cách sử dụng Convert.ToIn32
, tôi đang sử dụng nó như một công cụ để tính toán một số đầu ra có thể kiểm tra riêng biệt khác của lớp. Tôi không cần kiểm tra Convert
đã được gọi chính xác khi kiểm tra xem "1" + "2" == "3". Ghi nhật ký là khác nhau, đó là một đầu ra hoàn toàn độc lập của lớp. Tôi cho rằng đó là một đầu ra có giá trị với bạn, nhóm hỗ trợ và doanh nghiệp. Lớp của bạn không hoạt động nếu việc ghi nhật ký không đúng, vì vậy các bài kiểm tra đơn vị không nên vượt qua. Bạn nên kiểm tra những gì lớp học của bạn đăng nhập. Tôi nghĩ rằng đây là đối số sát thủ, tôi thực sự có thể chỉ dừng lại ở đây.
Tôi cũng nghĩ rằng đó là một thứ hoàn toàn có khả năng thay đổi. Ghi nhật ký tốt không chỉ in chuỗi, đó là xem ứng dụng của bạn đang làm gì (Tôi là một fan hâm mộ lớn của ghi nhật ký dựa trên sự kiện). Tôi đã thấy đăng nhập cơ bản biến thành UI báo cáo khá công phu. Rõ ràng là dễ dàng hơn rất nhiều để đi theo hướng này nếu đăng nhập của bạn trông giống _logger.Log(new ApplicationStartingEvent())
và ít thích Logger.Log("Application has started")
. Người ta có thể lập luận rằng điều này đang tạo ra hàng tồn kho cho một tương lai có thể không bao giờ xảy ra, đây là một cuộc gọi phán xét và tôi tình cờ nghĩ rằng nó đáng giá.
Trong thực tế trong một dự án cá nhân của tôi, tôi đã tạo ra một giao diện người dùng không đăng nhập hoàn toàn bằng cách sử dụng _logger
để tìm ra ứng dụng đang làm gì. Điều này có nghĩa là tôi đã không phải viết mã để tìm ra ứng dụng đang làm gì và tôi đã kết thúc với việc ghi nhật ký vững chắc. Tôi cảm thấy nếu thái độ của tôi đối với việc đăng nhập là nó đơn giản và không thay đổi, ý tưởng đó sẽ không xảy ra với tôi.
Vì vậy, tôi đồng ý với Telastyn cho trường hợp đăng nhập.
Semantic Logging Application Block
. Đừng sử dụng nó, giống như hầu hết các mã được tạo bởi nhóm "mẫu và pratice" của MS, trớ trêu thay, đó là một phản mẫu.
Mối quan tâm cắt chéo đầu tiên không phải là các khối xây dựng chính và không nên được coi là phụ thuộc trong một hệ thống. Một hệ thống sẽ hoạt động nếu ví dụ Logger không được khởi tạo hoặc bộ đệm không hoạt động. Làm thế nào bạn sẽ làm cho hệ thống ít kết nối và gắn kết? Đó là nơi mà RẮN xuất hiện trong thiết kế hệ thống OO.
Giữ đối tượng là singleton không liên quan gì đến RẮN. Đó là vòng đời đối tượng của bạn trong bao lâu bạn muốn đối tượng sống trong bộ nhớ.
Một lớp cần một phụ thuộc để khởi tạo không nên biết liệu thể hiện của lớp được cung cấp là đơn lẻ hay tạm thời. Nhưng tldr; nếu bạn đang viết Logger.Instance.Log () trong mọi phương thức hoặc lớp thì mã có vấn đề (mùi mã / khớp nối cứng) là một thứ thực sự lộn xộn. Đây là thời điểm khi mọi người bắt đầu lạm dụng RẮN. Và các nhà phát triển đồng nghiệp như OP bắt đầu đặt câu hỏi chính hãng như thế này.
Tôi đã giải quyết vấn đề này bằng cách sử dụng kết hợp tính kế thừa và đặc điểm (còn được gọi là mixins trong một số ngôn ngữ). Đặc điểm là siêu tiện dụng để giải quyết loại mối quan tâm xuyên suốt này. Mặc dù đây thường là một tính năng ngôn ngữ nên tôi nghĩ câu trả lời thực sự là nó phụ thuộc vào các tính năng ngôn ngữ.