Mọi người có nên luôn biết API đang làm gì chỉ bằng cách xem mã?


20

Gần đây tôi đã phát triển API của riêng mình và với sự quan tâm đầu tư vào thiết kế API, tôi đã rất quan tâm đến cách tôi có thể cải thiện thiết kế API của mình.

Một khía cạnh đã xuất hiện một vài lần là (không phải bởi người dùng API của tôi mà trong cuộc thảo luận quan sát của tôi về chủ đề này): người ta nên biết chỉ bằng cách xem mã gọi API đang làm gì .

Ví dụ, xem cuộc thảo luận này trên GitHub cho repo diễn ngôn , nó diễn ra như sau:

foo.update_pinned(true, true);

Chỉ cần nhìn vào mã (mà không biết tên tham số, tài liệu, v.v.) người ta không thể đoán nó sẽ làm gì - đối số thứ 2 có nghĩa là gì? Cải tiến được đề xuất là có một cái gì đó như:

foo.pin()
foo.unpin()
foo.pin_globally()

Và điều đó làm sáng tỏ mọi thứ (lập luận thứ 2 là liệu có nên ghim foo trên toàn cầu không, tôi đoán vậy), và tôi đồng ý trong trường hợp này sau này chắc chắn sẽ là một sự cải thiện.

Tuy nhiên tôi tin rằng có thể có các trường hợp trong đó các phương thức để đặt các trạng thái khác nhau nhưng có liên quan về mặt logic sẽ được hiển thị tốt hơn dưới dạng một cuộc gọi phương thức thay vì các phương thức riêng biệt, mặc dù bạn sẽ không biết nó đang làm gì chỉ bằng cách nhìn vào mã . (Vì vậy, bạn sẽ phải dùng đến việc xem xét các tên tham số và tài liệu để tìm hiểu - cá nhân tôi sẽ luôn làm bất kể điều gì xảy ra nếu tôi không quen với API).

Ví dụ: tôi trưng ra một phương thức SetVisibility(bool, string, bool)trên FalconPeer và tôi thừa nhận chỉ nhìn vào dòng:

falconPeer.SetVisibility(true, "aerw3", true);

Bạn sẽ không biết nó đang làm gì. Nó đang thiết lập 3 giá trị khác nhau để kiểm soát "mức độ hiển thị" falconPeertheo nghĩa logic: chấp nhận các yêu cầu tham gia, chỉ với mật khẩu và trả lời các yêu cầu khám phá. Việc chia nhỏ này thành 3 lệnh gọi phương thức có thể dẫn đến người dùng API đặt một khía cạnh của "khả năng hiển thị" mà quên đặt cho người khác mà tôi buộc họ phải suy nghĩ bằng cách chỉ đưa ra một phương thức để đặt tất cả các khía cạnh của "khả năng hiển thị" . Hơn nữa, khi người dùng muốn thay đổi một khía cạnh, họ hầu như luôn muốn thay đổi một khía cạnh khác và bây giờ có thể thực hiện điều đó trong một cuộc gọi.


13
Đây chính xác là lý do tại sao một số ngôn ngữ có tên tham số (đôi khi thậm chí được thực thi tên tham số). Ví dụ: bạn có thể nhóm nhiều cài đặt trong một updatephương thức đơn giản : foo.update(pinned=true, globally=true). Hoặc : foo.update_pinned(true, globally=true). Vì vậy, câu trả lời cho câu hỏi của bạn cũng cần tính đến các tính năng ngôn ngữ, bởi vì API tốt cho ngôn ngữ X có thể không tốt cho ngôn ngữ Y và ngược lại.
Bakuriu

Đồng ý - hãy ngừng sử dụng booleans :)
Ven

2
Nó được gọi là "bẫy boolean"
user11153

@Bakuriu Ngay cả C cũng có enum, ngay cả khi chúng là số nguyên ngụy trang. Tôi không nghĩ rằng có bất kỳ ngôn ngữ trong thế giới thực nào mà booleans là thiết kế API tốt.
Doval

1
@Doval Tôi không hiểu những gì bạn đang cố nói. Tôi đã sử dụng booleans trong tình huống đó đơn giản vì OP đã sử dụng chúng, nhưng quan điểm của tôi hoàn toàn không liên quan đến giá trị được thông qua. Ví dụ: setSize(10, 20)không thể đọc được bằng setSize(width=10, height=20)hoặc random(distribution='gaussian', mean=0.5, deviation=1). Trong các ngôn ngữ có các tham số được đặt tên bắt buộc, booleans có thể truyền tải chính xác cùng một lượng thông tin như sử dụng enum / hằng số được đặt tên, vì vậy chúng thể tốt trong các API.
Bakuriu

Câu trả lời:


27

Mong muốn của bạn không chia nó thành ba cuộc gọi phương thức là hoàn toàn dễ hiểu, nhưng bạn có các tùy chọn khác ngoài các tham số boolean.

Bạn có thể sử dụng enums:

falconPeer.SetVisibility(JoinRequestOptions.Accept, "aerw3", DiscoveryRequestOptions.Reply);

Hoặc thậm chí là một cờ enum (nếu ngôn ngữ của bạn hỗ trợ nó):

falconPeer.SetVisibility(VisibilityOptions.AcceptJoinRequests | VisibilityOptions.ReplyToDiscoveryRequests, "aerw3");

Hoặc bạn có thể sử dụng Đối tượng tham số :

var options = new VisibilityOptions();
options.AcceptJoinRequests = true;
options.ReplyToDiscoveryRequest = true;
options.Password="aerw3";
falconPeer.SetVisibility(options);

Mẫu Parameter Object cung cấp cho bạn một vài lợi thế khác mà bạn có thể thấy hữu ích. Nó giúp bạn dễ dàng chuyển xung quanh và tuần tự hóa một tập hợp các tham số và bạn có thể dễ dàng đưa ra các giá trị "mặc định" của các tham số không xác định.

Hoặc bạn chỉ có thể sử dụng các tham số boolean. Microsoft dường như làm điều đó mọi lúc với API .NET Framework, vì vậy bạn chỉ cần nhún vai và nói "nếu nó đủ tốt cho họ, thì nó đủ tốt cho tôi".


Mẫu Đối tượng tham số vẫn có một vấn đề mà OP đã nêu: " Việc chia nhỏ này thành 3 lệnh gọi phương thức có thể dẫn đến người dùng API để đặt một khía cạnh của" khả năng hiển thị "mà quên đặt cho người khác ".
Lode

Đúng, nhưng tôi nghĩ nó làm cho tình hình tốt hơn (nếu không hoàn hảo). Nếu người dùng bị buộc phải khởi tạo và chuyển vào một đối tượng VisibilityOptions, nó có thể (với bất kỳ may mắn nào) nhắc nhở họ rằng có các thuộc tính khác trên đối tượng VisibilityOptions mà họ có thể muốn đặt. Với cách tiếp cận ba cuộc gọi phương thức, tất cả những gì họ phải nhắc nhở họ là nhận xét về các phương thức họ đang gọi.
BenM

6

Rõ ràng, luôn có ngoại lệ cho quy tắc, nhưng khi bạn tự giải thích rõ, có một số lợi thế nhất định khi có API dễ đọc. Các đối số Boolean đặc biệt khó chịu, vì 1) chúng không tiết lộ ý định và 2) chúng ngụ ý rằng bạn gọi một hàm, trong đó bạn thực sự nên có hai, vì những điều khác nhau sẽ xảy ra tùy thuộc vào cờ boolean.

Câu hỏi chính là: bạn muốn đầu tư bao nhiêu nỗ lực để làm cho API của bạn dễ đọc hơn? Càng hướng ra ngoài, càng có nhiều nỗ lực có thể dễ dàng được chứng minh. Nếu đó là một API chỉ được sử dụng bởi một đơn vị khác, thì đó không phải là vấn đề lớn. Nếu bạn đang nói về API REST nơi bạn dự định để cả thế giới đánh mất nó, thì bạn cũng có thể đầu tư thêm một số nỗ lực để làm cho nó dễ hiểu hơn.

Ví dụ của bạn, có một giải pháp đơn giản: Rõ ràng, trong trường hợp của bạn, khả năng hiển thị không chỉ là một điều đúng hay sai. Thay vào đó, bạn có cả một bộ những thứ mà bạn cho là "khả năng hiển thị". Một giải pháp có thể là giới thiệu một cái gì đó giống như một Visibilitylớp học, bao gồm tất cả các loại tầm nhìn khác nhau này. Nếu bạn tiếp tục áp dụng mẫu Builder để tạo các mẫu này, bạn có thể kết thúc bằng mã như sau:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
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.