Live data from Hacker News

Tour of our 250k line Clojure codebase

tech.redplanetlabs.com

141–150 of 237 posts

Re: Tour of our 250k line Clojure codebase

#141
post #82

Earlier quoted context omitted.

> If the object is created without exceptions I can be sure it is valid. Without some kind of custom validation system in place (JSON schema, property attributes, etc), that doesn't tell you much. With most serialization libraries I've used the default settings would let you deserialize {} into any class without an exception and all the properties would just have the default value for their type. Using stricter setti…

At the end of the day, when we're working with external data, no language is a silver bullet. But you can get pretty far by doing https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... and yelling at your colleagues when they don't. FWIW, this sort of thing can be done with a dynamic language, too. It's true that some static languages happen to be really far ahead of the curve with this sort of thing. But it…

That's an interesting perspective (and now that you mention it I can think of someone at work who loves types and also tends to break stuff). I myself love static types because it allows me to avoid certain classes of errors and I try my hardest not to introduce bugs. Static typing also greatly informs my workflow. I tend to practice type driven development to the extent possible so when I go back to dynamic languages I feel like I'm coding with one hand tied behind my back since I can't encode my assumptions in types and rely on the type checker to help me validate them.

Re: Tour of our 250k line Clojure codebase

#142
This is the first time I'm looking closely at Clojure code and it seems wholly antithetical to the practice of software engineering. Not very readable, too many ways to create magical jank, odd coding conventions, and it looks like refactoring and maintenance would be a nightmare. I'm very glad the community has had the collective sense to not adopt this widely.

Re: Tour of our 250k line Clojure codebase

#143

Is there more info about the tool itself that claims 100X decrease in application development cost? Quite the claim.

It does seem odd. Stealth product. Bold claims. 3 blog posts one of which is a funding announcement and the other two having nothing to do with the product. I am intrigued but also a bit skeptical.

if the cost of building a large scale, end to end application used to be:

$10M, the new cost will be $100k, or $10k, or less.

$1M, the new cost will be $10k, or $1k, or less.

Re: Tour of our 250k line Clojure codebase

#144
While I've used all of them, some of their listed libraries are a little dated IMO. Of course this true of almost all mature codebases.

For the sake of the less experienced, I'd point them to these substitutions in particular:

Compojure: Reitit (which they used in the front end too, so migration maybe in progress) would be my preference for backend routing.

Component: Integrant takes Component's ideas, but prefers the flexibility of multimethods operating on plain data to the typed records approach for defining systems.

Schema: Once very popular, but superseded by clojure.spec (bigger in scope) and to a lesser degree Malli for data schemas.

Potemkin: Avoid, handy for some internal code organisation purposes but hostile to tooling and debugging IMO.

Re: Tour of our 250k line Clojure codebase

#145

> Detecting an error when creating a record is much better than when using it later on, as during creation you have the context needed to debug the problem. This is a great insight no matter what language or framework you're operating in. Laziness has its virtues, but invariants, validity checks, run-time type checks, etc. should all be performed as early (and often) as possible - it's much easier to debug issues whe…

Yes, and this is where statically typed languages shine (in my opinion). I like programming in a style that makes heavy use of the type system to enforce this. For example when writing an api endpoint to create a task I would typically deserialise the json into a CreateTaskRequest. If the object is created without exceptions I can be sure it is valid. CreateTaskRequest implements the ToTask interface. The service lay…

I like this design for making requests, but I don't like it for serving requests. Because if you don't just make every field a String in CreateTaskRequest, and you want to examine it in some way, then you're losing information. E.g. if you make a field an int, and they provide a non-int value, your CreateTaskRequest can't hold that value so its just gonna have a 0.

A class full of strings feels like a code smell - but that is the proper representation of serving the request. And the alternative is needlessly restrictive. Ultimately it feels like pointless ceremony.

Re: Tour of our 250k line Clojure codebase

#146
I jumped to the comments first, and my initial thought was, “what would Nathan Marz think of all of this...and what is he investing in these days?”

I was honestly skeptical, reading all of this clojure-macro criticism (constructive for the most part), but trust was restored when I saw the author. Well done, sir! Excited to see the details.

Re: Tour of our 250k line Clojure codebase

#147
post #60

we have a clojure codebase that's about 100k lines. Honestly I'm kinda fed up with it. Certain 3rd party libs we've used have been abandoned. We wrote our own libs for a major framework and it is failing behind. Too many "I'm very clever" functions that are hard to understand and also have subtle bugs.

Obligatory evangalism: Considered Kotlin as a JVM-lang-of-choice? We use it on all our backends and we really love it.

Java 16 is good enough for us.

Re: Tour of our 250k line Clojure codebase

#148

Earlier quoted context omitted.

Yes, and this is where statically typed languages shine (in my opinion). I like programming in a style that makes heavy use of the type system to enforce this. For example when writing an api endpoint to create a task I would typically deserialise the json into a CreateTaskRequest. If the object is created without exceptions I can be sure it is valid. CreateTaskRequest implements the ToTask interface. The service lay…

I like this design for making requests, but I don't like it for serving requests. Because if you don't just make every field a String in CreateTaskRequest, and you want to examine it in some way, then you're losing information. E.g. if you make a field an int, and they provide a non-int value, your CreateTaskRequest can't hold that value so its just gonna have a 0. A class full of strings feels like a code smell - bu…

If a field is an int and the incoming data has a string the parsing shouldn't succeed - instead it should give an error like "expected an int but saw a string". Maybe include the string it saw in the error. Add in the JSON path for bonus debugging points.

You shouldn't get to the point of having a CreateTaskRequest with a 0 that shouldn't be there.

Re: Tour of our 250k line Clojure codebase

#149
post #95
post #60

we have a clojure codebase that's about 100k lines. Honestly I'm kinda fed up with it. Certain 3rd party libs we've used have been abandoned. We wrote our own libs for a major framework and it is failing behind. Too many "I'm very clever" functions that are hard to understand and also have subtle bugs.

Seconded. I work in a clojure codebase that were trying to get out of. There's just dead libraries everywhere and stuff that maintained by one person that gets no updates at all. That or we just end up making functional "wrappers" around Java libraries and at that point we might as well just write straight Java. Also yea everyone wants to be so damn smart having macros within macros within macros that no one knows wh…

I’m sorry you are having trouble with Clojure.

One thing to keep in mind is that Clojure has excellent interop with Java, and it is trivial to write a small Clojure wrapper that just does critical thing for your dependency injection system e.g. startup and shutdown, and just pass through a reference to the Java object when you need to make function calls.

Another thing to keep in mind is that Clojure code is extremely stable, so pure Clojure libraries often require minimal maintenance, and code written for Clojure 1.3 runs on 1.10 99.99% of the time with no modifications.

Re: Tour of our 250k line Clojure codebase

#150
post #114

Earlier quoted context omitted.

You implement your own custom validation. The point is to encode the fact that you've validated a value in its type. So you have e.g. DeserializeJSON (String) -> Foo, and then ValidateFoo(Foo) -> ValidatedFoo. And then all the business code works on ValidatedFoo.

I know the point, I'm a C# dev by trade, but if I'm going to implement validation that goes beyond static type checking, which is most of the time, I'd rather put it all in a single place and not have to deal with the type nuisance in every single line of code I write.

Types are a huge help for implementing validation in one place - by having separate types for non-validated and validated versions, you can get the compiler to ensure that every code path actually goes through validation. Static type checking isn't in opposition to custom validation code, it supports it.
Post reply on HN