Lập trình để sử dụng giao diện trong tương lai


42

Tôi có một đồng nghiệp ngồi cạnh tôi, người đã thiết kế một giao diện như thế này:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

Vấn đề là, ngay bây giờ, chúng tôi không sử dụng tham số "kết thúc" này ở bất kỳ đâu trong mã của chúng tôi, nó chỉ ở đó bởi vì chúng tôi có thể phải sử dụng nó một thời gian trong tương lai.

Chúng tôi đang cố gắng thuyết phục anh ấy rằng đó là một ý tưởng tồi khi đưa các tham số vào các giao diện không sử dụng ngay bây giờ, nhưng anh ấy vẫn khăng khăng rằng sẽ phải thực hiện rất nhiều công việc nếu chúng tôi thực hiện việc sử dụng "ngày kết thúc" một thời gian sau đó và phải thích ứng tất cả các mã sau đó.

Bây giờ, câu hỏi của tôi là, có bất kỳ nguồn nào đang xử lý một chủ đề như thế này của các bậc thầy về mã hóa "được tôn trọng" mà chúng ta có thể liên kết anh ta với?


29
"Dự đoán là rất khó, đặc biệt là về tương lai."
Jörg W Mittag

10
Một phần của vấn đề là nó không phải là giao diện tốt nhất để bắt đầu. Bắt Foos và lọc chúng là hai vấn đề riêng biệt. Giao diện đó đang buộc bạn phải lọc danh sách theo một cách cụ thể; một cách tiếp cận tổng quát hơn nhiều là truyền vào một hàm quyết định xem có nên đưa foo vào không. Tuy nhiên, điều này chỉ thanh lịch nếu bạn có quyền truy cập vào Java 8.
Doval

5
Để truy cập là điểm. Đơn giản chỉ cần làm cho phương thức đó chấp nhận các loại whos tùy chỉnh bây giờ chỉ chứa những gì bạn cần và cuối cùng có thể thêm endtham số vào đối tượng đó và thậm chí mặc định nó để không phá mã
Rémi

3
Với các phương thức mặc định của Java 8, không có lý do gì để thêm các đối số không cần thiết. Một phương thức có thể được thêm vào sau với một triển khai mặc định mà gọi đơn giản là định nghĩa với, giả sử , null. Các lớp triển khai sau đó có thể ghi đè khi cần thiết.
nhện của

1
@Doval Tôi không biết về Java, nhưng trong .NET đôi khi bạn sẽ thấy những điều muốn tránh phơi bày những điều kỳ quặc IQueryable(chỉ có thể thực hiện một số biểu thức nhất định) để mã bên ngoài DAL
Ben Aaronson

Câu trả lời:


62

Mời anh ấy tìm hiểu về YAGNI . Phần cơ sở của trang Wikipedia có thể đặc biệt thú vị ở đây:

Theo những người ủng hộ cách tiếp cận YAGNI, sự cám dỗ để viết mã không cần thiết vào lúc này, nhưng có thể trong tương lai, có những nhược điểm sau:

  • Thời gian sử dụng được lấy từ việc thêm, kiểm tra hoặc cải thiện các chức năng cần thiết.
  • Các tính năng mới phải được gỡ lỗi, ghi lại và hỗ trợ.
  • Bất kỳ tính năng mới nào cũng áp đặt các ràng buộc đối với những gì có thể được thực hiện trong tương lai, do đó, một tính năng không cần thiết có thể ngăn cản các tính năng cần thiết được thêm vào trong tương lai.
  • Cho đến khi tính năng này thực sự cần thiết, thật khó để xác định đầy đủ những gì nó nên làm và kiểm tra nó. Nếu tính năng mới không được xác định và kiểm tra đúng cách, nó có thể không hoạt động chính xác, ngay cả khi cuối cùng là cần thiết.
  • Nó dẫn đến sự phình to mã; phần mềm trở nên lớn hơn và phức tạp hơn.
  • Trừ khi có thông số kỹ thuật và một số loại kiểm soát sửa đổi, tính năng này có thể không được biết đến đối với các lập trình viên có thể sử dụng nó.
  • Thêm tính năng mới có thể đề xuất các tính năng mới khác. Nếu các tính năng mới này cũng được triển khai, điều này có thể dẫn đến hiệu ứng quả cầu tuyết đối với tính năng leo.

Các đối số có thể khác:

  • 80% chi phí trọn đời của một phần mềm dành cho bảo trì . Viết mã đúng lúc giúp giảm chi phí bảo trì: người ta phải duy trì ít mã hơn và có thể tập trung vào mã thực sự cần thiết.

  • Mã nguồn được viết một lần, nhưng đọc hàng chục lần. Một đối số bổ sung, không được sử dụng ở bất cứ đâu, sẽ dẫn đến lãng phí thời gian để hiểu tại sao lại có một đối số không cần thiết. Cho rằng đây là một giao diện với một số triển khai có thể làm cho mọi thứ chỉ khó khăn hơn.

  • Mã nguồn dự kiến ​​sẽ được tự ghi lại. Chữ ký thực tế là sai lệch, vì người đọc sẽ nghĩ rằng điều đó endảnh hưởng đến kết quả hoặc việc thực hiện phương pháp.

  • Những người viết các triển khai cụ thể của giao diện này có thể không hiểu rằng không nên sử dụng đối số cuối cùng, điều này sẽ dẫn đến các cách tiếp cận khác nhau:

    1. Tôi không cần end, vì vậy tôi sẽ đơn giản bỏ qua giá trị của nó,

    2. Tôi không cần end, vì vậy tôi sẽ ném một ngoại lệ nếu không null,

    3. Tôi không cần end, nhưng sẽ cố gắng bằng cách nào đó sử dụng nó,

    4. Tôi sẽ viết rất nhiều mã có thể được sử dụng sau này khi endcần thiết.

Nhưng hãy lưu ý rằng đồng nghiệp của bạn có thể đúng.

Tất cả các điểm trước đều dựa trên thực tế là tái cấu trúc là dễ dàng, vì vậy việc thêm một đối số sau này sẽ không đòi hỏi nhiều nỗ lực. Nhưng đây là một giao diện và như một giao diện, nó có thể được sử dụng bởi một số nhóm đóng góp cho các phần khác của sản phẩm của bạn. Điều này có nghĩa là việc thay đổi giao diện có thể đặc biệt khó khăn, trong trường hợp đó, YAGNI không thực sự áp dụng ở đây.

Câu trả lời của hjk đưa ra một giải pháp tốt: thêm một phương thức vào giao diện đã sử dụng không đặc biệt khó, nhưng trong một số trường hợp, nó cũng có một chi phí đáng kể:

  • Một số khung không hỗ trợ quá tải. Ví dụ và nếu tôi nhớ rõ (sửa tôi nếu tôi sai), WCF của .NET không hỗ trợ quá tải.

  • Nếu giao diện có nhiều triển khai cụ thể, việc thêm một phương thức vào giao diện sẽ yêu cầu phải trải qua tất cả các triển khai và thêm phương thức đó vào đó.


4
Tôi nghĩ điều quan trọng là phải xem xét khả năng tính năng này sẽ xuất hiện trong tương lai. Nếu bạn biết chắc chắn nó sẽ được thêm nhưng không phải bây giờ vì bất kỳ lý do gì thì tôi sẽ nói rằng đó là một lý lẽ tốt để ghi nhớ vì nó không chỉ là một tính năng "trong trường hợp".
Jeroen Vannevel

3
@JeroenVannevel: chắc chắn, nhưng đó là rất đầu cơ. Từ kinh nghiệm của tôi, hầu hết các tính năng tôi tin chắc chắn sẽ được triển khai hoặc bị hủy bỏ hoặc bị trì hoãn mãi mãi. Điều này bao gồm các tính năng ban đầu được đánh dấu là rất quan trọng bởi các bên liên quan.
Arseni Mourzenko

26

nhưng anh ấy cứ khăng khăng rằng sẽ phải hoàn thành rất nhiều công việc nếu chúng tôi thực hiện việc sử dụng "ngày kết thúc" một thời gian sau đó và phải điều chỉnh tất cả mã sau đó.

(một thời gian sau)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

Đó là tất cả những gì bạn cần, phương thức quá tải. Phương thức bổ sung trong tương lai có thể được giới thiệu một cách minh bạch mà không ảnh hưởng đến các cuộc gọi đến phương thức hiện có.


6
Ngoại trừ thay đổi của bạn có nghĩa là nó không còn là giao diện nữa và sẽ không thể sử dụng trong trường hợp một đối tượng xuất phát từ một đối tượng và thực hiện giao diện. Đó là một vấn đề nhỏ nếu tất cả các lớp thực hiện giao diện nằm trong cùng một dự án (đuổi lỗi biên dịch). Nếu đó là một thư viện được sử dụng bởi nhiều dự án, việc thêm trường gây nhầm lẫn và làm việc cho những người khác vào những thời điểm lẻ tẻ trong x tháng / năm tiếp theo.
Kieveli

1
Nếu nhóm của OP (lưu đồng nghiệp) thực sự tin rằng endtham số này là không cần thiết bây giờ và đã tiếp tục sử dụng endphương thức không có trong toàn bộ dự án, thì việc cập nhật giao diện là điều ít lo lắng nhất của họ bởi vì nó trở nên cần thiết để cập nhật các lớp thực hiện. Câu trả lời của tôi là nhắm mục tiêu cụ thể về phần "điều chỉnh tất cả mã", bởi vì có vẻ như đồng nghiệp đã suy nghĩ về việc phải cập nhật người gọi phương thức ban đầu trong suốt (các) dự án để giới thiệu tham số mặc định / giả thứ ba .
hjk

18

[Có] các bậc thầy mã hóa "được kính trọng" mà chúng ta có thể liên kết anh ta với [để thuyết phục anh ta]?

Kháng cáo lên chính quyền không đặc biệt thuyết phục; tốt hơn để trình bày một lập luận có giá trị cho dù ai nói nó.

Đây là một vô lý quảng cáo reductio sẽ thuyết phục hoặc chứng minh rằng đồng nghiệp của bạn bị mắc kẹt là "đúng" bất kể sự nhạy cảm:


Những gì bạn thực sự cần là

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

Bạn không bao giờ biết khi nào bạn sẽ phải quốc tế hóa cho Klingon, vì vậy bạn nên chăm sóc nó ngay bây giờ vì nó sẽ đòi hỏi rất nhiều công việc để trang bị thêm và Klingons không biết đến sự kiên nhẫn của họ.


22
You never know when you'll have to internationalize for Klingon. Đó là lý do tại sao bạn chỉ nên vượt qua a Calendarvà để mã khách hàng quyết định xem anh ấy muốn gửi một GregorianCalendarhay KlingonCalendar(tôi cá là một số đã làm một cái). Tât nhiên. :-p
SJuan76

3
Còn về StarDate sdStartStarDate sdEnd? Chúng ta không thể quên Liên bang sau tất cả ...
WernerCD

Tất cả những thứ đó rõ ràng là lớp con của hoặc chuyển đổi thành Date!
Darkhogg

Nếu chỉ có nó là đơn giản .
msw

@msw, tôi không chắc anh ấy đã yêu cầu "kháng cáo lên chính quyền" khi anh ấy yêu cầu liên kết đến "các bậc thầy về mã hóa được tôn trọng". Như bạn nói, anh ta cần "một lập luận có giá trị cho dù ai nói vậy". Những người "bậc thầy" có xu hướng biết nhiều về những người đó. Ngoài ra bạn quên HebrewDate startHebrewDate end.
phân tích

12

Từ góc độ công nghệ phần mềm, tôi tin rằng giải pháp thích hợp cho loại vấn đề này nằm ở mẫu trình xây dựng. Đây chắc chắn là một liên kết từ các tác giả 'guru' cho đồng nghiệp của bạn http://en.wikipedia.org/wiki/Builder_potype .

Trong mẫu xây dựng, người dùng tạo một đối tượng có chứa các tham số. Container tham số này sau đó sẽ được truyền vào phương thức. Điều này sẽ quan tâm đến bất kỳ sự mở rộng và quá tải các tham số mà đồng nghiệp của bạn sẽ cần trong tương lai trong khi làm cho toàn bộ mọi thứ rất ổn định khi cần thay đổi.

Ví dụ của bạn sẽ trở thành:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}

3
Đây là một cách tiếp cận chung tốt, nhưng trong trường hợp này dường như chỉ đẩy vấn đề từ IEventGettersang ISearchFootRequest. Thay vào đó, tôi muốn đề xuất một cái gì đó giống như public IFooFiltervới thành viên public bool include(FooType item)trả về việc có bao gồm vật phẩm hay không. Sau đó, việc triển khai giao diện cá nhân có thể quyết định cách họ sẽ thực hiện việc lọc đó
Ben Aaronson

@Ben Aaronson Tôi chỉ cần chỉnh sửa mã để hiển thị cách tạo đối tượng tham số Yêu cầu
InformedA

Trình xây dựng là không cần thiết ở đây (và khá thẳng thắn, một mô hình được sử dụng quá mức / quá mức nói chung); không có quy tắc phức tạp nào xung quanh cách các tham số cần được thiết lập, vì vậy một đối tượng tham số hoàn toàn ổn nếu có mối quan tâm chính đáng về các tham số phương thức phức tạp hoặc thường xuyên thay đổi. Và tôi đồng ý với @BenAaronson, mặc dù quan điểm của việc sử dụng một đối tượng tham số không phải là bao gồm các tham số không cần thiết ngay lập tức, nhưng làm cho chúng rất dễ dàng để thêm sau này (ngay cả khi có nhiều triển khai giao diện).
Aaronaught

7

Bạn không cần nó ngay bây giờ, vì vậy đừng thêm nó. Nếu bạn cần nó sau, mở rộng giao diện:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

+1 vì không thay đổi giao diện hiện tại (và có thể đã được thử nghiệm / giao hàng).
Sebastian Godelet

4

Không có nguyên tắc thiết kế nào là tuyệt đối, vì vậy trong khi tôi hầu như đồng ý với các câu trả lời khác, tôi nghĩ tôi sẽ đóng vai người ủng hộ quỷ dữ và thảo luận về một số điều kiện theo đó tôi sẽ xem xét chấp nhận giải pháp của đồng nghiệp:

  • Nếu đây là API công khai và bạn dự đoán tính năng này hữu ích cho các nhà phát triển bên thứ ba, ngay cả khi bạn không sử dụng nội bộ.
  • Nếu nó có lợi ích đáng kể ngay lập tức, không chỉ những người trong tương lai. Nếu ngày kết thúc ngụ ý là Now(), thì việc thêm một tham số sẽ loại bỏ tác dụng phụ, có lợi ích cho việc lưu trữ và kiểm tra đơn vị. Có lẽ nó cho phép thực hiện đơn giản hơn. Hoặc có thể nó phù hợp hơn với các API khác trong mã của bạn.
  • Nếu văn hóa phát triển của bạn có một lịch sử có vấn đề. Nếu các quy trình hoặc lãnh thổ khiến việc thay đổi một thứ trung tâm như giao diện trở nên quá khó khăn, thì điều tôi đã thấy trước đây là mọi người thực hiện các giải pháp phía khách hàng thay vì thay đổi giao diện, thì bạn đang cố gắng duy trì hàng tá ngày kết thúc đột xuất bộ lọc thay vì một. Nếu điều đó xảy ra rất nhiều ở công ty của bạn, thì nên cố gắng hơn một chút để chứng minh trong tương lai. Đừng hiểu sai ý tôi, tốt hơn là thay đổi quy trình phát triển của bạn, nhưng điều này thường dễ nói hơn là làm.

Điều đó đang được nói, theo kinh nghiệm của tôi, hệ quả lớn nhất đối với YAGNI là YDKWFYN: Bạn không biết bạn cần hình thức nào (vâng, tôi vừa viết từ đó lên). Ngay cả khi cần một số tham số giới hạn có thể tương đối có thể dự đoán được, nó có thể ở dạng giới hạn trang hoặc số ngày hoặc boolean chỉ định sử dụng ngày kết thúc từ bảng tùy chọn người dùng hoặc bất kỳ số lượng nào.

Vì bạn chưa có yêu cầu, nên bạn không có cách nào để biết loại tham số đó sẽ là gì. Bạn thường kết thúc với một giao diện khó xử không phù hợp nhất với yêu cầu của bạn hoặc dù sao cũng phải thay đổi nó.


2

Không có đủ thông tin để trả lời câu hỏi này. Nó phụ thuộc vào những gì getFooListthực sự làm, và làm thế nào nó làm điều đó.

Dưới đây là một ví dụ rõ ràng về một phương thức có hỗ trợ một tham số bổ sung, được sử dụng hay không.

void CapitalizeSubstring (String capitalize_me, int start_index);

Việc thực hiện một phương thức hoạt động trên một bộ sưu tập trong đó bạn có thể chỉ định bắt đầu nhưng không phải là kết thúc của bộ sưu tập thường là ngớ ngẩn.

Bạn thực sự phải xem xét vấn đề và hỏi xem tham số này có vô nghĩa trong bối cảnh của toàn bộ giao diện hay không và thực sự gánh nặng bao nhiêu cho tham số bổ sung.


1

Tôi sợ đồng nghiệp của bạn thực sự có thể có một điểm rất hợp lệ. Mặc dù giải pháp của ông thực sự không phải là tốt nhất.

Từ giao diện được đề xuất của anh ấy rõ ràng rằng

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

đang trả lại các trường hợp được tìm thấy trong một khoảng thời gian. Nếu khách hàng hiện không sử dụng tham số kết thúc, điều đó không thay đổi thực tế là họ mong đợi các trường hợp được tìm thấy trong một khoảng thời gian. Điều đó chỉ có nghĩa là hiện tại tất cả các khách hàng sử dụng khoảng thời gian kết thúc mở (từ đầu đến vĩnh cửu)

Vì vậy, một giao diện tốt hơn sẽ là:

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

Nếu bạn cung cấp khoảng thời gian với một phương thức tĩnh nhà máy:

public static Interval startingAt(Date start) { return new Interval(start, null); }

sau đó khách hàng thậm chí sẽ không cảm thấy cần phải xác định thời gian kết thúc.

Đồng thời giao diện của bạn truyền tải chính xác hơn những gì nó làm, vì getFooList(String, Date)không truyền đạt rằng điều này liên quan đến một khoảng.

Lưu ý rằng đề xuất của tôi xuất phát từ những gì phương pháp hiện đang làm, không phải từ những gì nó nên hoặc có thể làm trong tương lai và như vậy, nguyên tắc YAGNI (thực sự rất hợp lệ) không áp dụng ở đây.


0

Thêm một tham số không sử dụng là khó hiểu. Mọi người có thể gọi phương thức đó giả sử tính năng này sẽ hoạt động.

Tôi sẽ không thêm nó. Việc thêm nó sau này là không quan trọng bằng cách sử dụng cấu trúc lại và sửa chữa một cách máy móc các trang web cuộc gọi. Trong một ngôn ngữ gõ tĩnh, điều này rất dễ thực hiện. Không cần thiết phải háo hức thêm nó.

Nếu có một lý do để có mà tham số bạn có thể ngăn chặn sự nhầm lẫn: Thêm một khẳng định trong việc thực hiện giao diện để thực thi mà tham số này được thông qua như là giá trị mặc định. Nếu ai đó vô tình sử dụng tham số đó ít nhất anh ta sẽ thông báo ngay lập tức trong quá trình kiểm tra rằng tính năng này không được triển khai. Điều này sẽ loại bỏ nguy cơ trượt một lỗi vào sản xuất.

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.