0

I'm working on a library of C++ wrappers/bindings, for another, C-ish, API.

The C'ish API has a "launch" function, which, among other things, takes a launch configuration structure (well, sort of, I'm simplifying things). Not every point in the space of values for this structure is valid. For example, it has a num_widgets field, which is integral, but a system only has a limited number of widgets, so if you exceed it - the launch will fail.

It's cumbersome for the user to just up'n'construct a full-fledged launch configuration. So, I provide a builder class which helps the user do it more conveniently and easily. The builder has methods for making all sorts of setting, such as setting the number of widgets, and a build method which produces a launch configuration.

Finally, one of the design goals of my wrapper class is to avoid excessive use of the underlying API, and perhaps even to minimize such excessive use. Not that using it is prohibitively expensive, but still.

Now for my question:

Should my build() method - or even earlier methods which set the number of widgets - check, as soon as is possible, that there are enough widgets on the system for the configuration to be usable? Or, alternatively, should I just let the user build what they asked for, and have the launch itself fail due to the invalid config? With the second alternative, note that I could furnish the builder with some sort of a validation method which could look this up.

I'm interested in what is customary in similar or somewhat-corresponding scenarios, and what users would are more likely to expect.

Notes:

  • Assume that nobody builds launch configurations for use on other systems via serialization, or for simulation of other systems etc.
  • There are no abstract and concrete builders, there's just one builder class and that's it.
  • My library relies on C++11 or later being used, but I don't think that matters.
  • The underlying API's launch function will reject an invalid configuration, but will often not tell you why it was rejected.
asked Feb 4, 2024 at 20:16
8
  • 1
    are both your options "error at run time"? ideally if you are using this kind of fluent builder structure you should attempt to error at compile time. otherwise, not sure it makes a difference Commented Feb 4, 2024 at 20:50
  • Given that this sounds like configuration testing; are you sure you really want to validate these things at runtime? I generally prefer performing configuration testing as part of the development lifecycle. If your target users are developers using the API, then I'd expect it would be useful for them to be able to verify their configuration as part of their CI/CD pipelines, or as some other automated job in a test environment. Commented Feb 4, 2024 at 21:02
  • @Ewan: The validity of the configuration depends on settings made at run time and the state of the system (number of widgets) at run time. Commented Feb 4, 2024 at 21:07
  • @BenCottrell: To the extent I actually understand your comment: 1. My users are developers using the API, yes. 2. Whether it's in a CI/CD pipeline or not - validation is only possible at run-time. Commented Feb 4, 2024 at 21:09
  • so... both methods have the same code structure, error within milliseconds of each other, throw the same error message and happen at runtime? what's the difference? Commented Feb 4, 2024 at 21:11

1 Answer 1

2

Take a look at how others are doing it. A similar situation to yours would be launch templates in AWS for EC2 instances, or Lambda functions or pretty much any "auto scaling" component in cloud infrastructure. Those configurations are valid, however, you do have runtime limits on how many instances you can actually physically run using those launch templates. When you reach those limits, you will get errors at runtime.

From my understanding, your builder creates objects that can be valid at point X in time, but invalid at X + 1, and technically someone else does the expensive operation of figuring out if it is valid. If we go with the SRP and separation of concerns principles, then the builder should not do the expensive runtime check. But instead have someone else do the check and return errors. This has the following advantages:

  1. Consumers may be able to retry using that configuration object at a later time, without rebuilding it from scratch. If you are a library developer that allows its consumer developers to input random data in there, your library may be used in situations when rebuilding by itself can be expensive, without actually checking at runtime.
  2. Builders typically don't throw unpredictable (or in your case expensive/difficult to predict) errors, so this would also mean less surprises for whoever uses your builder.
  3. By separating those 2, it will be easier for you to change runtime validation at a later point.

In retrospect, application code itself is a builder pattern that is only validated at runtime. Having possible runtime errors normally doesn't prevent you from successfully compiling the code.

answered Feb 5, 2024 at 14:10

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.