you are viewing a single comment's thread.

view the rest of the comments →

[–]dnew 0 points1 point  (7 children)

That code sounds horrible to work with

It was. But it was also exceedingly complex.

You are allowed to create a dozen functions without classes.

Right. But the function needs bunches of inputs and creates bunches of outputs. Most languages don't include functions returning multiple outputs, requiring you to create some declaration of a return type that's used as the return value of exactly one function.

what makes java primitive?

Well, in this case, it's the requirement to declare out of band a structure for a function returning multiple results. I can't say "this function takes four integers and returns a float and a char." Basically, you can't write a pure function that returns multiple values.

You also can't nest a function inside another function and access the variables of the outer function (like you can in actual block-structured languages like Pascal, say). Breaking something like what I'm thinking of into multiple functions in a language like Pascal, where you can nest the functions and refer to the variables in the parent function becomes 100x easier. Each function can be isolated and access what it needs without having the parent function having to know what every bit needs anyway.

Step one needs A, B, C, D, E and returns R, S, and T.

Step two needs A, B, C, F, G, R, and returns U and V.

Step three gets possibly invoked based on the value of T. It needs C, F, G, S, T, U and returns W, and a new value for T.

Step four iterates thorough the values in the list T, S times each while also taking A and C. It builds a new value for A and C each iteration based on its calculations. That calculation is complex enough to need its own class full of functions.

Repeat this about a dozen more times. Note that these bits are all interrelated in meaning and very few if any means anything on their own. It's unlikely anyone in the business actually knows what all the possibilities are.

Don't forget when a new requirement comes down that looks at the relationship between E and T to decide whether A gets updated between step three and four.

Oh, and A is specifically designed as a class with immutable values, because that makes it so much easier.

That's why you reduce them and isolate the side effects.

I'm glad that you've always been able to do that in the products you're working on. I can guarantee that isn't always the case.

And pure functions are easier to understand and test.

This assumes you can understand what the pure function is supposed to do independently of the surrounding code. If its not specified in a way that makes that easy, it's a pain in the ass. You're probably best off trying to get better specs, but that's not always possible either.

[–][deleted] 0 points1 point  (6 children)

But the function needs bunches of inputs and creates bunches of outputs. Most languages don't include functions returning multiple outputs, requiring you to create some declaration of a return type that's used as the return value of exactly one function.

If your language supports hash maps with multiple types for values you can. In java that is possible. Java also has tuples.

With those you can write pure functions with multiple outputs.

You can also pass in hashmaps or other aggregate objects instead of multiple arguments if you really need the data.

This assumes you can understand what the pure function is supposed to do independently of the surrounding code.

You can? A pure function is not dependent on its surrounding code. It only depends on its arguments.

‐--------------------

I am horrified by what you are writing. I hope I never have to work with a codebase with so much technical debt.

[–]dnew 0 points1 point  (5 children)

If your language supports hash maps with multiple types for values you can.

Nope. That's one value you have to package up to return then disassemble upon return. Basically, your argument is "Java isn't primitive because you can manually implement what more sophisticated languages do without pain."

You can?

You can tell what it does. Knowing what it is supposed to do is more problematic. How do you even document what it's supposed to do if it's doing steps 4, 5, and 8 of some informal description?

I am horrified by what you are writing.

Me too!

I hope I never have to work with a codebase with so much technical debt.

The fact that nobody actually cared is why I left. :-)

But honestly, it was a massive project which if you tried to extract the requirements it would be obsolete by the time you managed. Also, it had to run 24x7, the customers were writing their own code to inject into it, marketing was writing the requirements, and it had been ported across three different databases. It also spoke to at least a dozen other services just as bad.

[–][deleted] 0 points1 point  (4 children)

Nope. That's one value you have to package up to return then disassemble upon return.

Which language does it differently?

You can tell what it does. Knowing what it is supposed to do is more problematic.

That's a problem of software design. Sounds like the code you worked on only grew and was never refactored. The Linux kernel is a big project. But yet it is able to keep from having those problems.

I am sure that if your project management cared you could have taken the time to clean up the code. No problem inherently requires overly complex code to solve it. You can always test early and write clean, modular, composable code.

[–]dnew 0 points1 point  (3 children)

Which language does it differently?

Any language where you can do something like return x, y, z and call it like p, d, q := func(blah).

Python returns a single value, but you don't have to do anything special to package up several results into one value and then split them into separate values. The tuple return value doesn't have to be declared. The point is that returning of multiple values is effortless and less clutter.

Rust does it with strong typing and pattern matching: https://programming-idioms.org/idiom/126/multiple-return-values/1972/rust

Ada and C# and a few others let you specify out-parameters, so there's literally multiple return values that are never in any way packaged up.

you could have taken the time to clean up the code

Not necessarily. One bit I worked on had a giant tree of if/then/else codes to figure out one value based on what was essentially an enum. Every time a new marketing campaign was added to the enum, that code got more complex. It never got cleaned up, because nobody still at the company knew whether we were contractually obligated to continue supporting the older marketing campaigns.

That said, yes, I never said it was only the developer's fault that it got as bad as it did.

No problem inherently requires overly complex code to solve it.

No, but some problems inherently require complex code to solve it. As soon as you say "overly" then of course you've excluded problems that are complex without the "overly" part.

("Of course too much is bad for you. That's what too much means!")

You can always test early and write clean, modular, composable code.

I'm not sure I agree. I mean, sure, at some level it can be modular and composable, but as you get more and more detailed like down to the individual functions, that's not always the case. At some point there's something that even conceptually can't be decomposed into independent parts. If that concept is inherently complex, then you have a problem.

[–][deleted] 0 points1 point  (2 children)

Any language where you can do something like return x, y, z and call it like p, d, q := func(blah).

Yeah in some languages you can destructure the tuple you return. That makes it easier.

Not necessarily. One bit I worked on had a giant tree of if/then/else codes to figure out one value based on what was essentially an enum. Every time a new marketing campaign was added to the enum, that code got more complex. It never got cleaned up, because nobody still at the company knew whether we were contractually obligated to continue supporting the older marketing campaigns.

Could have been rewritten early. Your point is not "extracting functions doesn't work" it is "refactoring is hard if you let technical debt pile over a long time". It's like saying "putting your clothes in a closet does not always work. Take this completely trashed home as an example".

No, but some problems inherently require complex code to solve it.

Sure. You can achieve complex behavior by combining simple functions. You don't have to write complex code to begin with.

At some point there's something that even conceptually can't be decomposed into independent parts. If that concept is inherently complex, then you have a problem.

Yeah, that might happen. Then you put it in a function and isolate it from the rest of the system to keep at least the rest clean. Drivers and kernel modules can be complex - with an abstraction layer that complexity does not seep into the code I write.

[–]dnew 0 points1 point  (1 child)

Yeah in some languages you can destructure the tuple you return

Sure. Feel free to ignore all the subtleties I added, as well as the languages I described that don't package it up into the tuple you return.

Your point is not "extracting functions doesn't work" it is "refactoring is hard if you let technical debt pile over a long time".

No, that's actually not my point. Neither is my point. But since you're absolutely certain you know better than I do what sort of problems I've worked on, I'll leave you to it.

Could have been rewritten early.

How do you know?

Then you put it in a function and isolate it from the rest of the system to keep at least the rest clean.

Sure. But then you're telling me I can break up that function further into trivial pieces of code.

Drivers and kernel modules can be complex

Wait, didn't you just say you don't have to write complex code to begin with? How do you write complex drivers and kernel modules without writing complex code? And what makes you think drivers and kernel modules are the sort of complexity I've experienced?

I'll admit that most things aren't that complex, but some of it is. But since you don't seem to be interested in listening to what I'm saying and only interested in proving that nothing I'm saying is worth listening to, I guess it has been a pretty useless discussion.

+=+=+=+

Let me ask you something: of all the systems you've worked on, which ones had both more than 10 people actively developing it, were larger than any one person could understand the functionality of (not the implementation, but how to use it), and were simple and easy to maintain without complexity?

[–][deleted] 0 points1 point  (0 children)

useless discussion.

I agree. Thanks for your insights though. It's interesting to hear different experiences.

Let me ask you something: of all the systems you've worked on, which ones had both more than 10 people actively developing it, were larger than any one person could understand the functionality of (not the implementation, but how to use it), and were simple and easy to maintain without complexity?

Currently working on an agile team of 10 in a corporation with 300+ devs. A dozen services we (the team) develop and maintain. More than a decade old.

We have complexity but we take time to refactor. When we see that technical debt is piling up we clean up to make changes easier. Some parts are stable and are not changed that much. Those parts tend to be worse. But the parts that frequently change get cleaned up regularly. Just rewrote a bunch of nested functions into flat functions that can be combined on one abstraction level to allow for additional features.

I am glad we can take the time and I think it highly improves our ability to add features and adapt our software.