10

I've seen such a convention. Whenever a public method is declared, two classes are also defined that enclose its return value and parameters like this:

public MethodNameReturnDTO MethodName(MethodNameParametersDTO parameters)
{
    // method body
}
public class MethodNameParametersDTO
{
    // blah
}

public class MethodNameReturnDTO
{
    // blah
}

This is the case even if one of the above classes has no fields or properties.

What is the purpose of doing so? What are its pros and cons?

On the first glance I can only see cons, but it is very possible I'm missing something.

NB: Tagging this question C# because this is where I saw this convention, but I guess this Q could be tagged language agnostic just as well? Idk.

7
  • 1
    Win32, and Google Protobufs also do this, but native code forward compatibility. I don't think that reason applies to C#. Mar 7 at 15:41
  • 7
    The purpose is to make you appreciate language constructs like Javascript object literals, where you don't need such mindless busywork. :-)
    – user949300
    Mar 7 at 16:17
  • 3
    the main issue in this example is the naming convention
    – njzk2
    Mar 7 at 18:47
  • 5
    I have done so in the past and it had a technically valid explanation to do it and I was going to explain it in an answer. But then I remembered that the business requirement that formed the need for this technical implementation was utterly stupid, so I will not make it an answer. "Because sometimes you work for incompetent clowns" does not make for a good answer, it could be "the" answer to basically any question and also no constructive answer at all.
    – nvoigt
    Mar 8 at 10:57
  • 1
    @Bergi One of them was to never under any circumstance throw exceptions, so we had all result classes derive from the same base, so we could catch exceptions and attach the respective error codes to the reply objects in one central place. So basically every single outgoing method's signature was public DerivedFromBaseResponse FunctionName(DerivedFromBaseRequest request) so our handler could find and handle those methods. That whole requirement was one persons brainfart, because throwing exceptions in that interface (SOAP) was well defined. But I guess the decider was afraid of them.
    – nvoigt
    Mar 8 at 11:44

8 Answers 8

17

Whether it's a widespread convention (I hope it's not), I don't know, but I agree with @Telastyn—see their answer. Those are the same reason why many people keep naming classes with DTO.

I will ignore the DTO thing on purpose and focus on reasons to encapsulate inputs and outputs.

What is the purpose of doing so?

Above all, expressiveness. Allow me to use Message and Receipt to elaborate on this point and the following ones.

boolean send(string subject, string body, string target, string[] cc, boolean isHtml) 
//and 
Receipt send(message)

Which one do you think causes less cognitive load?

Thought work, next time you speak to a coworker about messages instead of messages, say string subject, string body, string target, string[] cc, boolean isHtml. After a while, get back to message and note if you feel somewhat "released".

This is a naive example of course, but try to apply this rationale to your current project. Make your code to be readen in the same "ubiquitous" language you speak when talking about the business or the domain you are modelling.

Other purposes

  1. Abstraction.

    The definition of a Message can change over time. The same rationale applies to the response Receipt. Changes can be motivated by new requirements. For example, supporting different channels (email, SMS, cloud notifications, event-driven communications, etc.)

  2. Encapsulation and Data consistency.

    You tie up information that shares the same lifecycle. The information is validated, consumed and discarded together as a single unit of information. The correctness/consistency of these units must be ensured at the same time and in the same place.

  3. Testing

    If you need to test logic based on the Message or Receipt state (isValid() or isSent() or acknowledged()), the only points to tackle/mock are those two elements. That can be complicated if Receipt is more than a single flag or Messages has different states (created, sending, pending, awaiting ack, rejected, accepted, deleted)

What are its pros and cons?

I have listed some of the pros. But, as usual, the pros and cons are contextual. Those are "pros" only when they are relevant to you and that's contextual. It changes or evolves.

The cons are inherent to the cons of over-engineering, being complexity the most important one. Unnecessary complexity to be precise. Unnecessary complexity is followed by code harder / more expensive to maintain. Another con derives from abstractions not backed by facts (reasons to exist). These abstractions with no clear direction or focus are candidates to become a sort of fit-all-solution. Ultra generic/abstract designs bring the seed of evil.


1: For example, if we narrow the context to MVC's controllers or REST controllers/clients we wouldn't see much inconvenience.

14
  • 16
    This answer is not bad, but it could be caring a little bit more for the details of the actual question. Message is a good abstraction, but SendParametersDTO is not. A boolean return value as a primitive is fine, a class SendReturnDTO just for encapsulating a boolean isn't very sensible.
    – Doc Brown
    Mar 7 at 9:03
  • 1
    Message is just an example. For guidance. The OP's snippets are meaningless so it's hard to say if that boolean (as a response) is ok or not. The same for the input argument. That's why, at the end of my answer, I leave it open to interpretation. The OP should do all the assertions to determine if custom parameters and responses are adequate or not. That's contextual. Regarding DTOs, the question is broad enough as for not to narrow the answer to the common gather up data from multiple data sources in a single response. That would answer 50% of the question.
    – Laiv
    Mar 7 at 10:31
  • 3
    @gaazkam Presumably if you have a good type system and like to code in such a way that you leverage the type checker as much as possible, the compiler will instead flag all the places where the Message object is created. The Message object then allows the compiler to distinguish between places that are forwarding calls specified elsewhere (which won't need to change) and those that actually decide the call parameters. But if you code your "DTO"s to have an empty constructor and then set the fields individually, then no, the compiler won't help much.
    – Ben
    Mar 8 at 5:02
  • 1
    Message is just an example. this is an example for a case where there might be a chance that consolidating the paramteres into a class would make sense, but it is not the example that helps OPs case. OPs example is meaningless, as you say, and that is exactly the question - why do some people use meaningless class abstractions for parameters - it is not about creating a valid domain class instead of passing its attributes around piecemeal, it is about creating a class which specifically encapsulates the parameters of a (each) specific method.
    – AnoE
    Mar 8 at 13:14
  • 2
    I'm missing a small but significant pro: Auto complete by the editor. It saves time because you just type ->get and autocomplete offers all relevant info. And it saves typos. You might know the names, but your colleagues (and you in 6months) dont.
    – Martijn
    Mar 8 at 14:16
16

What is the purpose of doing so?

Potentially, it allows you to violate the open closed principle and add things to these DTOs without impacting the rest of the code as much. It also potentially satisfies some arbitrary coding standard, or some terrible generic abstract class that needs generic parameters for its inputs and outputs.

Occasionally, this is the result of code generation. If an API is defined in something like Thrift, these input/output classes are generated based on that. Most codegen tools would be smart enough not to generate empty classes, but not all.

Mostly though, it is some foolish dogma that people follow because they’re “supposed to”. If your function has too many arguments, hiding them within an aggregation structure doesn’t reduce coupling or complexity.

1
  • 1
    Note that there is a use for DTOs derivated from an abstract class (or in C#, encapsulated in a generic container class): It allows standardizing success/error returns (or, in the input case, prerequisites to all functions, like a user token that requires validation). But if there's no common base (or API boundary), I don't see the interest.
    – Medinoc
    Mar 8 at 17:50
4

Mainly, it comes down to readability and maintainability. For example, let's say that you're using a method to create a new user, and this method can accept the first name, last name, email, username, street, city, state, postal code, country, preferred language, preferred currency, and a dozen other properties. Even if you have some kind of auto-complete hinting system, you now have to decide how you're going to remember which parameter is which when you decide to add a new property, which leads to something like:

String firstName = 'First';
String lastName = 'Last';
String email = 'example@example.com';
String username = 'username';
String street = 'Street';
String city = 'City';
String state = 'State';
String postalCode = '12345';
String country = 'US';
String language = 'en';
String locale = 'en_US';
String timezone = 'UTC';
String defaultApplication = 'Home';

UserDomain.createUser(
    firstName,
    lastName,
    email,
    username,
    street,
    city,
    state,
    postalCode,
    country,
    language,
    locale,
    timezone,
    defaultApplication
);

If this API ever changes, your code may break, because you can't guarantee the order is correct anymore. This increases maintenance requirements because you now need to remember a very long list of parameters.

In addition, some languages have a maximum number of parameters you can supply to a method. In the server-side language of Salesforce, the Apex Code runtime limits a method to 32 parameters. If you need more than that, you're now writing so-called "DTO" (Data Transfer Objects) classes to handle that data anyways. Further, most languages allow you to only return one return value. If the result may include multiple values (e.g. isSuccessful/errorMessage), it's also helpful to have such an object to represent this type of output.

You might liken this to the difference between a SOAP/RPC and REST endpoint. With SOAP or RPC, the parameters are always in order, and any deviation will result in a runtime error. In those situations, developers typically end up using some kind of DTO object anyways, which mitigates errors caused by revisions to the API. REST, by contrast, tends to use JSON. The great thing about JSON is the parameters are almost always wrapped up in an object anyways. The parameter values don't care about their order within the object (at least, in any sane implementation of JSON parsing).

However, if there are no properties to accept/return, I would state that this falls squarely in the YAGNI (You Ain't Gonna Need It) rule. If a method is designed to never return a value, it should be a void (or equivalent in the language) return type. Returning anything else increases cognitive load by suggesting that some type of return value is possible. Clear, concise code should strive to not increase cognitive load unnecessarily. Similarly for the input parameters, if there's no expected input, there should be no such parameter defined.

Personally, I'm against calling a class a DTO class unless it does, in fact, cross the wire from one device to another (typically client-server, but also possibly client-client or server-server). The name of the class and its context should be enough to explain the nature of the class. I would expect a User class to have the properties of a user. It may also have methods to manipulate that data, or that code might be in a domain layer that uses this object to deal with users; that should be clear from context.

So, the pros for a "DTO" class, whether or not it is called a DTO, is that it greatly improves maintainability and readability. While I could see an argument against such objects for literally one parameter, it really comes down to a design choice. When there is nothing to transfer in a given direction, there should be no parameters, or a void return type.

2
  • 1
    I'd probably subdivide many of those Strings into objects called "name" (or ID), and "address", and consider not use a raw String for many of them. But still a good answer and I've seen lots of similar "quick and dirty" code with overuse of strings.
    – user949300
    Mar 8 at 16:48
  • 1
    @user949300 I agree with that, the example was meant to be cringe-y and simple on purpose. I've even seen many examples of a language having an assert method like assert(expected, actual), and devs manage to get those backwards, leading to nonsensical-sounding errors, like "expected a dozen to be 12: expected: 15, actual: 12". Languages with named parameters are much better off.
    – phyrfox
    Mar 8 at 16:57
2

Although with a strong WEB framework you can parse HTTP requests into your parameter list without pain, there are still reasons why DTOs are preferred.

The pattern of data transfer objects is tailored to the nature of WEB APIs, where connections can be unstable, and the cost could be high. So, it's best to design wide APIs with many parameters and attach as much information as proper in the return value.

As a side effect, the signature of a WEB API is likely to grow very large, so you wrap them in DTOs for simplicity, at least at the first glance.

Personally, I recommend against DTOs for all WEB APIs. It's perfectly fine to have a few strings in the parameter list. However, I've seen some coding standards that require everything wrapped in a DTO. I would say they designed this for the sake of easy management.

1
  • 2
    There are other benefits to having DTOs... Such as forcing you to decouple your business logic from what essentially amounts to a view model (in the case of APIs being called by front-ends). You might also have to deal with stuff like adding in attributes to ensure stuff is serialised to the correct names etc. Then you can map that onto an object that your domain actually understands and has some internal meaning which doesn't care about that. It's a separation of concerns thing. That being said, that's a comment on WebAPIs, not this question which seems to be about internal function calls Mar 7 at 15:57
1

Oh well. I feel like someone is overdoing it.

Let’s say you have an argument “name” of type string. It may be better to have a “name” type that may include first name, last name, and other things as needed. Gives you a proper abstraction. Makes sure to the compiler that “name” and “city” won’t be confused.

An abstraction for names is useful. An abstraction for “parameters passed to function x” or “return value of function x” isnt.

0

In most cases I think this is probably a bad convention, but there is one case I can think of where something like this is a good idea. If you use SignalR, the recommendation is to always use an object as a parameter for functions you call via SignalR and put all parameters for that function on the object.

The reason is that changing the parameters of a function is a breaking change and will cause any old client to throw errors if such a function is called with different arguments. But adding more properties to an object parameter is allowed and won't be a breaking change for older clients. So you can add more parameters to a function that way without breaking old clients that only understand the function with fewer parameters.

So if some kind of RPC and serialization is used with these functions, it could be that always using a class makes it easier to add parameters without making it a breaking change. But that depends heavily on the actual mechanism.

3
  • "adding more properties to an object parameter is allowed" - I can only imagine that to work when they're optional
    – Bergi
    Mar 9 at 11:59
  • @Bergi of course your code has to work with both the old object and the new one with more properties. But it no longer is an automatic breaking change that requires to update both the server and client at the same time because the function signature changed. Mar 9 at 12:34
  • Are you suggesting that the old object and the new object would have different types if they have different properties?
    – Bergi
    Mar 9 at 13:39
-1

While I certainly wouldn't advocate always doing this, there are sometimes advantages to be gained by wrapping multiple parameters within an object, particularly if there is useful validation that can be done that would otherwise have to be done within the function body, cluttering up the actual functionality, e.g.

void ExecuteWithdrawal(WithdrawalInstruction instruction) {
  ... reduce account by amount instructed
}

class WithdrawalInstruction{
    ctor(string accountNumber, decimal withdrawalAmount){
        if (withdrawalAmount < 0) throw ArgumentException("...");
        if (!IsAccountNumberValid(accountNumber)) throw ArgumentException("etc");
        
        WithdrawalAmount = withdrawalAmount;
        AccountNumber = accountNumber;
    }
    decimal AccountNumber { get; }
    decmal WithdrawalAmount { get;}
}
1
  • 1
    The question asks specifically about doing this for all return values and arguments.
    – Bergi
    Mar 8 at 16:45
-6

From the same reason that distinguishes between using which or what questions.

Here is a different perspective:

    programming paradigms
        imperative: describes the process of getting the result
        declarative: describes the result

    programming paradigm sub sets
        imperative: procedural, object oriented
        declarative: functional, logic, mathematical, reactive

    programming languages

    programming techniques
        optimistic or asynchronous programming - succeed or re-try until succeed
        pessimistic or synchronous programming - prepare to succeed

Is “programming paradigm sub sets” the proper terminology? Is this summary close to reality? If not which or what are the differences?

Differently said enclosing all return values and arguments of a method in separate classes is a way to be prepared for unprepared.

3
  • 3
    This answer does not seem to be related to the question at all...
    – AnoE
    Mar 8 at 13:17
  • …or at best, only the last sentence is. I can't make sense of the rest.
    – Bergi
    Mar 8 at 16:44
  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Mar 11 at 2:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.