4 ms·
LLMs are always going to generate output (prose or code) in the same style as the training data. The Java training data corpus is both bloated/Enterprise-y, and
by derefr 2y ago
LLMs are always going to generate output (prose or code) in the same style as the training data. The Java training data corpus is both bloated/Enterprise-y, and mostly written by junior engineers or even students (because there are so many publicly-accessible Java school-project personal GitHub repos.)
If you're working on a Java project, consider prompting the AI to first write a "pseudocode solution" in a more concise/low boilerplate/"highly expressive" language — Ruby, for example — and then asking it to translate its own "pseudocode" into Java.
(Mind you, I'm not sure if you can modify the implicit system prompt used by automatic coding-assistance systems like Cursor. [Can you? Anyone know?] I'm more just thinking about how you'd do this if you were treating a ChatGPT-like chatbot as if it were StackOverflow — which is personally where I find most of the value in LLM-assisted coding.)
Alternately, consider prompting the AI to "act like a senior $lang engineer forced to write in Java for this project, who will attempt to retain their existing opinionated coding style they learned from decades of experience in $lang, in the Java code they write" — where $lang is either, once again, a more expressive language; or, more interestingly, a language with a community that skews away from junior engineers (i.e. a language that is rarely anyone's first programming language) and toward high-quality, well-engineered systems code rather than slop CRUD code. For example, Rust, or Erlang/Elixir.
(Funny enough, this is the exact same mental through-line that would lead a company to wanting to hire people with knowledge of these specific languages.)
- jprete 2y agoI think you misunderstood the OP; they were comparing two different conditions (Java slop vs. LLM slop), because they think there's a common cause (tooling lets people make overly complex systems that still kinda work but are difficult to understand). As far as I could tell the OP was not saying, at all, that the LLM slop was written in Java.
- srinathkrishna 2y ago> mostly written by junior engineers or even students (because there are so many publicly-accessible Java school-project personal GitHub repos.) I used to work at a Java shop a few years ago. Hated my time there since a. I was coming from a more pragmatic way of writing software in C and Java and its ecosystem made me pull my hair out and b. came across several very senior engineers who insisted on writing code with factories and builders and obscure design patterns which made no sense to me. They were all straight from praying at the altar of Design Patterns and always found a way to give comments about a future scenario where this would be refactored. I was fairly convinced that design patterns in OOP languages were conceived by relatively senior folks as a form of gate keeping.
- manmal 2y agoWhile rooted in actual use cases, I agree that often the GoF and other patterns are applied when there’s no need. My guiding principle nowadays is deletability/unpluggability - structuring things in a way that removing a component or behavior is as easy as possible. I do need the same patterns to achieve this, but I don’t blindly apply them, and use them way less as a result. Eg, with Swift‘s value types and great closure support, I can often go 90% of the way just with structs - no interfaces (protocols) or classes needed.
- PhilipRoman 2y ago(rant incoming) I wish C was better. A company I have to interact with at $work managed to involve 10 different processes just to change wifi network name (it all boils down to a single netlink message to the driver). And eventually there were too many daemons so they replaced the entire thing with a new architecture where everything ran in one process but with many threads, with no regard for thread safety and blindly copy pasting hostapd code all over the place. Everything is passed and parsed as a string, so you get none of the advantages of C (speed, simplicity) and all of the drawbacks (ABI breaking, etc.). To make matters worse, these people apparently haven't heard of databases or even plain text files. Data is stored in XML or JSON "databases", but of course having a full XML parser is too expensive so it's actually their own homemade XML parser which requires line breaks in certain places and literally does strchr('<'). The funniest thing was when they replaced dbus with their proprietary data bus (with zero debugging capabilities of course) because dbus was "too slow", meanwhile at the same time their "hashmap" is actually a linked list (!!!) because they thought it would be premature optimization to write a proper map type. All written in C and yet somehow slower than python. I'm really looking forward to the stone age of programming, because we're definitely not there yet.
- derefr 2y ago> came across several very senior engineers who insisted on writing code with factories and builders and obscure design patterns which made no sense to me. In a large-enough Java codebase, with good software engineering being done, you will eventually hit the problems for which these weird kinds of indirection are the solution (because Java is dumb and forces them) — but it tends to take a while, and not involve concerns that are the first priority in a greenfield project. I'll just address the factory-pattern here, but the builder-pattern has a similar story. The Java factory-uber-alles mode of thought (i.e. "make all your Java classes factories; and all your static methods, including constructors, into instance methods on those factories") exists because doing any kind of sum-typing on Class objects in Java is painful. Java has interfaces. But Java has no concept of type-level interfaces. In theory, Java could have: 1. extended the definition of interfaces, to validate that certain static methods and constructor signatures exist within the Class objects of implementing classes; 2. and then introduced something like a ClassWithInterface<T> type, that a Class object could be cast to if-and-only-if that class (or one of its ancestors) implements that interface. But Java never did this. And even if it did, by definition, a Java interface must still be known to the class defining it, and the membership of the class in the interface declared explicitly by the implementing class. Which means it's a bit too late to make this change now, for it to ever have any kind of useful impact — there's so much existing Java code that can't be changed [old stable JDK versions everyone depends on; third-party libraries that are basically abandonware; etc], which would therefore not have defined membership in whichever later-defined type-level interfaces these types really should have type-level membership in. And even if this were part of Java from the beginning, you'd still have the situation where you want to "make" a third-party class part of a type that the maintainer of that class doesn't think it should be a member of. (Maybe because that type-level interface is a private one you're defining within your app!) With type-level interfaces, you could put a Class object into a type-level-interface-typed variable, and then use that variable to invoke the static methods of that class, construct instances of the class, etc. But because you don't have type-level interfaces, to interact with a Class object "generically", you instead have to resort to using the java.lang.reflect package — which, depending on the method, either 1. accepts and returns plain Objects (resulting in extremely error-prone code); or 2. requires that you gradually find the right method handle (and catch five possible exceptions); pack the parameters for calling it (and catch five more possible exceptions); call it (and catch five possible exceptions besides the ones the typing yourself); do any return-type coercion yourself (three more exceptions)... and where all of those exceptions are extremely opaque and obscure the "actual" error (e.g. the caller passing an argument of the wrong type.) Which means that any time you might want to take code that calls a static method on a Class, and instead make that code call a static method on the value of a Class-typed variable... you're now in for a world of hurt. Dependency injection? Mocks in unit tests? Strategy/adapter patterns? Plugins systems? All become the hairiest Java code in the world. ...until eventually you get fed up, and realize that you can have type-level interfaces today, by just taking the statics and constructors on these classes (that you may or may not own); creating equivalent wrapper instance methods on little proxy classes you do own; and then make all those little proxy classes implement an interface. Bing-boom-bam, you now have a "type-level-interface"-typed variable (i.e. a variable typed as the regular interface type implemented by these proxy classes), that you can (through these proxies) invoke these static methods and constructors on. What do you call these little proxies only intended to wrap statics and constructors? Well, how about "factories", since statics+constructors tend to mostly be used to make instances?
- duskwuff 2y ago> LLMs are always going to generate output (prose or code) in the same style as the training data. There's also an implicit bias towards blathering in LLM output, especially in open-ended scenarios like code completion. Language models prefer high-probability outputs; if it's appropriate for a model to generate highly stereotypical content like boilerplate getters/setters and documentation comments, it's more likely to generate that than something more application-specific like the actual implementation of an algorithm.