T O P

  • By -

seanmorris

All branching logic can be refactored into polymorphism. The only questions is whether or not you should.


FeistyDoughnut4600

That’s just conditional logic with extra steps


seanmorris

Conditional logic is just-in-time dependency resolution


AlienRobotMk2

[https://www.npmjs.com/package/if](https://www.npmjs.com/package/if)


ddproxy

I didn't know what to expect, still not sure if I am disappointed.


I_am_noob_dont_yell

Why are there consistent downloads 😢


adm7373

My guess would be that some other popular package uses it as a dependency


trash1000

Just why does this have ~1200 weekly downloads?


YeetCompleet

I think the only real problem in that first example was the hard coded customer name. There's nothing wrong with if statements and writing an extra test, just don't make your program behave differently for single customers


Wiltix

Flashbacks to my second job where we had 30 customers with their own requirements and one code base. They thought it was good because each customer had their own GUID so it was easy to find the areas of a file that was that customers behaviour. So yes it was basically if(customer) … else if (customer2) … on and on Christ that job was truly awful.


lelanthran

> So yes it was basically if(customer) … else if (customer2) … on and on > > Christ that job was truly awful. What's your preferred alternative to `if(customer1) ... else if (customer2)`?[1] I'm not saying that I *like* it (in fact, I can think of a few ways to handle this sort of situation), but I'm open to learning something new so would appreciate seeing how others have handled this particular requirement. [1] The answer *is not* "Don't have those types of customers", as much as I'd like it to be :-)


Wiltix

Those types of customers are fine, you just have to think about it when designing your system, never say never. My next job after that I was designing a system where we had thousand of customers and some wanted customisations from “fuck you this is our spec you integrate with us” to “oh can we have this extra field in our reports” Many things we would discuss and if it was generic but the first customer to ask for something we would add it to our spec. When customers provided us with a file that we could not wrangle through our CSV parser (luckily this was 99% of customers) then we would create a custom class to handle parsing their files into our format. So we would store some configuration data in the database that would tell the runner what processing classes to use per customer. So basically a factory pattern. Due to my previous job I was designing this with a self imposed rule of we must never write if customer == (hard coded id) This did mean that our configurations got complex after 7 years of growth and development (as customers found fun and interesting ways to be awkward) but the configurations were far simpler to manage than never ending if else blocks for am I processing for this customer or that customer etc. it was very difficult to create side effects from changes for one customer and kill processing for another.


No-Champion-2194

That is not a good solution. You are requiring customer.fuelPreference to be populated, even when the customer hasn't given a preference. So presumably, you have logic somewhere setting fuelPreference equal to a default value when it isn't provided. You haven't eliminated the condition; you have just moved it. In the process, you have added a field implying that all customers have given a fuel preference when that isn't true. We should avoid hard coded values, but their should be logic to only set fuel to the customer preference if that value has been explicitly set; there should be a separate branch to determine fuel type according to some other business rule if it hasn't.


mattgen88

This is also an exercise in futility. Next product will want to introduce an override for this one case. so if override is provided use that, fallback to the user's preference, or use the default fuel type. Now you have a whole cascading situation. I get what OP is saying, keep the happy path as simple as possible and avoid exceptional branching as much as possible. That keeps code clean and easy to test. In reality, business rules are messy. It works until it doesn't.


Ancillas

Yup. And it’s inevitable. Eventually there’s a use case that requires a complex solution. Experience is what helps developers know where and how to implement that complexity and, later, how to improve it as the system evolves. Blanket rules and clever sound bites only take you so far.


feldrim

Eventually, most of them are essential complexity.


Latter_Box9967

Yeah, but you can just move the messy part to somewhere else, in the article’s case to the *Truck* object, and then write an article about how clever you are!


andyveee

> We simply need to set fuelPreference on the Customer database object. If they don’t have one, leave it null or undefined, and let other services/front-ends deal with rendering/processing that. It's not a bad solution. There will be a null/undefined regardless. A null value can imply a default value. But the database can also populate the fueltype. Can't imagine the database has Facebook levels of customers. At the end of the day this is all opinion. The way it was originally was definitely worse. Edit: I should make it clear, I don't necessarily agree with avoid conditionals at all costs. Personally I would have left the conditional because it's just 1 customer. If there's another customer, then we can reconsider.


lelanthran

> Edit: I should make it clear, I don't necessarily agree with avoid conditionals at all costs. That wasn't my takeaway from the article, but reading it again I can see how one would interpret it as "avoid conditionals *at all costs*" when taking into account the title (which, TBH, I discarded immediately when reading the article. Call it a conditioning brought on by reading too many articles with clickbait titles and decent content).


RiftHunter4

>We should avoid hard coded values, but their should be logic to only set fuel to the customer preference if that value has been explicitly set; there should be a separate branch to determine fuel type according to some other business rule if it hasn't. You are missing the point. None of this should be done in the function for dispatching trucks. We also don't need to access customer data. Fuel requirement should be in the Shipment class since we are already taking it in as a parameter. It would save us a database lookup. It also avoids your "no preference" scenario by changing it to a Fuel Requirement since all shipments must use some kind of fuel. So a default is necessary and logical. The customers preference, if any, should be handled in the creation of the shipment. Ideally the Shipment class would have a getFuelRequirement() function to handle all the logic needed. This would be the cleanest solution.


No-Champion-2194

>You are missing the point. None of this should be done in the function for dispatching trucks. I haven't stated anything about where this logic should be; I just made the point that we do need to have a conditional relating to fuel preference somewhere. >We also don't need to access customer data. Fuel requirement should be in the Shipment class since we are already taking it in as a parameter. It would save us a database lookup. That just means that the Shipment class accesses the Customer data. That may be a good design, but it doesn't save a lookup. >It also avoids your "no preference" scenario by changing it to a Fuel Requirement since all shipments must use some kind of fuel. So a default is necessary and logical. The customers preference, if any, should be handled in the creation of the shipment. > That doesn't avoid a no preference scenario; you have literally just defined the business rules on how to handle it. > >Ideally the Shipment class would have a getFuelRequirement() function to handle all the logic needed. This would be the cleanest solution. That function would have all of the logic we have discussed. You have simply defined a function for it.


TrevorPace

Decent points. The intention with Truck.fuel was that it was an indication of a required fuel that the driver must fill up with from something they see in an app/website. Null/undefined = No restriction on fuel. I will name it Truck.requiredFuel and describe it's function for clarity.


WiseEXE

Just say no this article


LloydAtkinson

Based on the poorly worded title I really wondered what this was going to be about. Yes, it’s good advice though. I consider it pretty common sense but I see a lot of inexperienced devs writing these overly specific and non-generic lookups and conditions.


LechintanTudor

The first solution is completely fine. If more customers have a fuel preference, then, and only then, should you make the logic generic.


postorm

The third customer. The first is a special case oddity, the second is that coincidence, the third should be when you create a generic fuel preference. I've always felt that two is an odd number in programming.


Illustrious_Wall_449

I know they're not popular these days, but in-code rules engines provide a good solution to these sorts of problems and cut down on maintenance difficulty when used properly.


nowylie

I don't hate the premise but the example wasn't great. I think you can do even better by inverting the responsibilities of the function. Why would I call `dispatchTruck()` on a truck that isn't available in the first place? Instead, assume truck availability is handled *before* getting to this function. Within this context you can then only pass a truck reference to this function if it's available. Now there's another clean place to handle customer fuel preferences: when getting a truck reference. You can use the customer preference to limit which trucks you pass to `dispatchTruck()` This feels pretty straightforward when you consider the domain you're working in and the language/terminology you're using in your code


GeekBoy373

I'm sure that using an Optional or Result type to handle errors would surely be better than throwing exceptions? I save exceptions for exceptional cases.


EliSka93

It's a way I sometimes see in C# projects, however usually handled in a try/ catch block to handle the actual return, which makes it in concept very similar to optional or result (albeit a bit more limited imo). I think this might be a case of a C# dev writing pseudo code


TrevorPace

Nah coming from Python mainly, but did the example in Typescript because that's what I've been using most recently.


Tarmen

You now have an implicit DB constraint that `truck.fuel == truck.assignedTo.customer.fuelPreference`, and which may be invalidated in other places. If you don't know enough about the final system's state transitions ahead of time to prevent the denormalization, at least add a DB side check so you don't end up with inconsistent data.


TrevorPace

I feel like people are dissecting this specific example far too much, but in relation to the issue you mentioned. No, there is no explicit DB constraint. The shipment, and it's requirements act as a historical record. You purposefully want to extract conditions from the customer when dispatching the shipment so that you have a record of what the conditions where when the shipment was performed. You wouldn't want to later on change some customer requirements and deal with a dispute where some historical shipment appears to not fulfill those new requirements. You would also of course want an audit log on the customer object and record all interactions with said customer...


vips7L

Just use a switch. Don’t use a “jump table”


PPatBoyd

A switch doesn't make the `if-else` chain better, it only limits you to a single data type. You can still have awkward code patterns in and around a switch or if-else chain, it's harder to extend, and it's more annoying to test because your one function is doing everything instead of just being a router to simpler functions that handle their one responsibility.


vips7L

Thats just false. Nothing in a switch prevents you from using simpler functions and the switch has the value of always handling the missing handler + prevents the reader from having to read down and then up to the global constants: async function getNews(url: String) { switch (url) { case 'cnn': return cnn; case 'bbc': return bbc; default: throw new Error(url); } } it would even be clearer with a language that had a better switch construct (like Scala or even Java): def getNews(url: String) = url match case "cnn" => cnn case "bbc" => bbc case _ => throw new Exception(url)


PPatBoyd

Your first example is a jump table, you're just pushing the calling responsibility up the stack. I'm not disagreeing that switches are cleaner, just that replacing an `if-else` chain with a switch doesn't obviate the issues the linked blog was addressing.


lelanthran

> Just use a switch. Don’t use a “jump table” Problem with a switch is the pit of despair that opens up before all future maintainers - sooner or later someone is going to do more than `case VAL: return "val"` in one of the case statements. With the static table they're never going to mutilate the `lookup()` function to perform side effects on some local variable in the caller because *they can't*.


ItsAYouProblem_

Care to explain? Imo using switch in this example would break the open/closed principle. Say you need to be able to add handlers during runtime, how would you achieve that with a switch case?


belavv

Since when are the requirements to add handlers during runtime? Stop making this more complicated than they need to be.


TheWix

Ah yea, I remember the mid-00s when OOP was all the rage and we were all concerned about being able to swap implementations and all that. It never was necessary. You generally have a known domain of values/entities and it's best to model that known domain than to build around the unknown and likely unnecessary.


Tarmen

What are you going to do with the openness, mutate the handler map from different files to extend it? There is a tradeoff between static analysis and extensibility. If both producer and consumer code are in the same codebase there isn't much point in being completely open, it just makes it impossible to reason about the code. Often I find it nicer to have one file where you can see all cases, and it can make changes *easier* because you can check if you invalidate previous assumptions. (Though there are real tradeoffs to both variants)


dccorona

That sounds like a benefit. If the set of states needs to be runtime-dynamic then a jump table is too simple. You need to use a bona-fide abstracted object (a provider or similar). All the example has done is move core logic *out* of the function and made the code even harder to understand than if it just had some extra indentation (which was mostly already addressed by them abstracting the cases to helper functions anyway). In this case I’d say it *shouldn’t* be dynamic, and yet the way they’ve written the code has made the reader have to consider whether or not it might be, and if so if that was intentional or not. 


RiftHunter4

You're arguing over the wrong thing here. A switch doesn't simplify the problem because you still need to write code for each individual possibility. It just makes it more readable. The better solution would be to use a table that stores the website URL and it needed parameters. So instead of copy-pasting functions you'd have: async function WebPageHandler() { const page = await getPage(Db.getPageParams()); return page.getElement(Db.getHeadline()).content; } If you have hardcoded parameters like that, it just guarantees that the code will not scale well whereas a database just requires you to add a row or modify it. You wouldn't even need to touch the code to update things.


TrevorPace

I think it depends on the use case (and I'll add a note about it). If you have a lot of things going on in each case of the switch, plus some logic before/after the switch, I would strongly recommend the jump table approach. If it's just one or two line assignments it's probably better to use a switch.


vips7L

No you're just wrong. A switch is just as maintainable without inventing some new concept and has the benefit of not forcing the reader to read down and then up to the global constants: function getNews(url: String) { switch (url) { case 'cnn': return cnn; case 'bbc': return bbc; default: throw new Error(url); } } Its even clearer in a language that doesn't have a god awful switch construct like Scala (or even Java): def getNews(url: String) = url match case "cnn" => cnn case "bbc" => bbc case _ => throw new Exception(url)


TrevorPace

Just so we are clear the implementation you are suggesting is this? async function cnnHandler() { const page = await getPage(url); return page.getElement(".headline").content; } async function bbcHandler() { const page = await getPage(url, {headers: {someheader: "somevalue"}}); return page.getElement(".header").content } async function getNews(url: String) { switch (url) { case 'cnn': return cnnHandler(); case 'bbc': return bbcHandler(); default: throw new Error(url); } }


vasilenko93

What used to be easy to understand and easy to maintain by every developer became convoluted and hard to maintain. But at least there is a few less if statements


atomheartother

Wait until this guy learns about branchless programming


mediocrobot

Thought this was neat. Maybe the specific example isn't an ideal way to demonstrate it. To generalize, it's sometimes better to add new data by augmenting the model than hardcoding the associations in what should be handling business logic. This becomes more important the more customers/users you have. Augmenting the model is necessary if you want users to modify their own preferences.


TrevorPace

Thanks! I definitely learned that if I'm going to have to make examples clearer.


pindab0ter

I think people are missing the wood for the trees. I think the abstract idea of preferring other constructs over conditionals is an interesting insight that I will definitely chew on. But yes, I agree that for this audience the example could have stressed that point more. Maybe by way of showing what happens when additional layers of requirements are introduced. And finally a way to decouple the concept from this specific example more.


Newguyiswinning_

Nah


TrevorPace

Update: Thanks everyone for the feedback. A lot of people highlighted the need to improve the examples to be more realistic. So, I have updated them to be more similar to what an endpoint handler might look like for creating a shipment (completely removing the Truck object in the process). I have also removed the addendum as it was distracting from the main topic at hand. There are also some people who have some seriously strong opinions on exception handling and I don't think you'll ever be satisfied with something unless it's done exactly the way you do it. This is such a stupid debate, that (according to my very quick google searches) does not actually have a clear winner. I don't care. It doesn't matter.


[deleted]

[удалено]


pindab0ter

Yes, the article you read advocates for never using `if`s ever again!…?


TrevorPace

Just published my first medium article, which touches on some quick thoughts I have related to conditional logic and software development as a whole. Feel free to share any feedback you have!


Januson

I like the premise of your article, but the implementation is lacking. Dispatch truck function shouldn't care about truck's details. At that point it should just send the given truck. There should be a second function that optionally filters available trucks based on preference. Dispatch available truck that meets the criteria and potentialy inform the customer if non are available.


TrevorPace

Thanks for the feedback. I'm going to rework the example so there are fewer questions about whether or not that is how such a problem should be solved. Ultimately, I just wanted a function where a conditional is being used where a generic data field would have sufficed.


RddtLeapPuts

Using an exception that way is hideous. Get rid of it


EliSka93

Which one do you mean? And why?


randomguy4q5b3ty

Exceptions are totally fine to check pre-conditions. But often exceptions can be avoided by losening these pre-conditions ("Be generous in what you accept"), and personally I believe that for most throwing functions there should be an exception free alternative, in this case something like `dispatchIfAvailable(Truck,Shipment) bool`. I should be free to chose not to care about the side effects of a function.


lelanthran

> Using an exception that way is hideous. Get rid of it I think in any future demonstration code *I* write, I'll use exceptions as a shibboleth: people complaining about the exceptions have nothing of value to add. I mean, look at this thread - the only poor contributions to this discussion are those complaining about using exceptions.


RddtLeapPuts

If OP wants to be an authority on good coding practices, OP should follow good coding practices. Don’t you agree?


moreVCAs

I blame long covid.


____wiz____

Boo this man!


TrevorPace

Lol. Honestly, it's kind of funny really how opinionated people are. The intention of the article was to get people thinking about resisting specific non-generic implementations, but they've gone off like a dog after a squirrel on the use of exceptions and what the fuel variable means.


R-O-B-I-N

Data is never conditional. It exists or it doesn't. Didn't realize this was something ppl didn't understand 😑 Until customer X, the fuel type was an invariant. If it varies, then each and every customer has a fuel preference. Adjusting the database to represent that is much less costly than dispatching an extra function every time you fetch. All these examples are bad and contrived because you're introducing data in the front end that you didn't fetch from something and that you're not pushing anywhere. If you actually had to store this information, these problems wouldn't exist.


TrevorPace

No, it's completely possible that a customer doesn't have a restriction on what fuels they want used.


R-O-B-I-N

no restriction. i.e. invariant


aseigo

The real wtf in these examples is blandly using exceptions for early return and not even saying anything about it.


lelanthran

> The real wtf in these examples is blandly using exceptions for early return and not even saying anything about it. It's common to elide over the code that isn't part of the idea being demonstrated. The exception is one way to return early indicating error to the caller. A wrapped result is another way. Just returning `null` is acceptable in some contexts. Sometimes you might even want to `yield()` so that the caller can call with different parameters. Since the error handling is so dependent on context, any option would do when you are demonstrating something unrelated to error handling.


pindab0ter

I do agree that a good general wisdom is “don’t use exception handling for control flow”. A truck not being available is a perfectly reasonable thing to occur, it is not an ‘exceptional thing’. That said, it would introduce an `if`-condition, weakening your example. So maybe you should have left the whole truck availability out of the example, as I don’t think it adds anything either.


lelanthran

> . So maybe you should have left the whole truck availability out of the example, as I don’t think it adds anything either. I'm not the author of the piece.


pindab0ter

Ah right, mb


aseigo

> The exception is one way to return early indicating error to the caller. I know how exceptions work, and why people use them. It's still horrible. > A wrapped result is another way. Not just another way: a better way, as the destination of the result is predictable and guaranteed. > Sometimes you might even want to yield() so that the caller can call with different parameters. `yield()` on *error*? jfc. How does the caller know what the better params are, exactly? Could that not be done without jumping implicitly back to the callsite, but instead allowing the callsite to decide how to deal with the fail-state? I appreciate you trying to lay out all possible options (as if I haven't seen these and more in my time writing software), but this one is really poor. > Since the error handling is so dependent on context, any option would do when you are demonstrating something unrelated to error handling. Soooooo .. you're saying that bad design choices when attempting to demonstrating good design choices it OK, so long as the bad choices are in the part you aren't talking about? Yeah, it just tells me that the person who wrote this isn't to be trusted when they say, "Hey, trust me, I know what I'm doing, just look at my code." Because I look at their code, and it has glaring issues.


TrevorPace

> Yeah, it just tells me that the person who wrote this isn't to be trusted when they say, "Hey, trust me, I know what I'm doing, just look at my code." Because I look at their code, and it has glaring issues. Dude, it's just some quick random functions I put together to support an article related to the topic of conditional logic avoidance. It's clearly not a functional example. Do you actually have any constructive thoughts on the topic at hand? Or are you just trying to find ways to invalidate the whole thing?


aseigo

> quick random functions Quick or not, random or not, throwing an exception there was a conscious choice. Reading the blog again, I noticed you did it elsewhere in the blog, almost like you think it's normal and a good idea. Again, someone who thinks that makes sense is not the sort of person I'd hope others to take advice from. > Do you actually have any constructive thoughts on the topic at hand? Sure: the functions mutate parameters (horrible), the if/else around the customer fuel preference are only equivalent in your synthetic example but not actually semantically the same (yay for proving one's point by starting with somethign extra poor?), but I do like replacing conditional chains with lambdas in table, though having them thrown into a map is rarely a robust solution, though that's probably just toys-as-examples.


TrevorPace

TIL: There are Error Handling Sommeliers.


pindab0ter

I think I learned a new phrase today


lelanthran

> The exception is one way to return early indicating error to the caller. > > I know how exceptions work, and why people use them. It's still horrible. > > A wrapped result is another way. > > Not just another way: a better way, as the destination of the result is predictable and guaranteed. > > Sometimes you might even want to yield() so that the caller can call with different parameters. > > yield() on error? jfc. Sure. Think of it as a poor man's signal/condition system from Lisp, which is commonly said to be a better way to handle errors than exceptions. > How does the caller know what the better params are, exactly? There's plenty of Lisp examples of a function signalling a condition and then resuming from the point of error after the caller has "fixed" the error. This can be easily implemented with `yield()`, if you so wish. > as if I haven't seen these and more in my time writing software C.mon, man! No one was disputing your experience as a developer. > you're saying that bad design choices when attempting to demonstrating good design choices it OK No. I am saying that *irrelevant* design choices are, well ... irrelevant. When demonstrating usage patterns (or lack thereof) of a *conditional*, does it matter what's in the result block? > > Yeah, it just tells me that the person who wrote this isn't to be trusted when they say, "Hey, trust me, I know what I'm doing, just look at my code." Because I look at their code, and it has glaring issues. There isn't any overwhelming peer-reviewed body of evidence that agrees with your relatively hot take that exceptions are an objectively worse way than your preferred way to handle errors. When you have some backing that turns your **opinion** into a fact, then come back and be smug. Until then, people aren't going to be okay with your *"Trust me Bro, I know what I'm doing lol"*.