4 ms·
(working full-time on JavaScript code for last 4 years) TL;DR good dynamic codebase is possible, but not when you throw too many devs at it (particularly junio
by jakub_g 11y ago
(working full-time on JavaScript code for last 4 years)
TL;DR good dynamic codebase is possible, but not when you throw too many devs at it (particularly junior devs) and only when you set the very high quality bars. Otherwise, in finite time the codebase will converge to a big pile of mess.
IMO the big dynamic-language codebase with multiple people working on it can only survive with extensive test suite, lots of mock data available, automated quality tooling (jshint etc) and proper code review in place. All of those are well-known best practices, but they require good developers and certain discipline.
In particular, when the app retrieves lots of data from a server, it's easy to get lost when the frontend app doesn't even know what data it operates on, it just knows it gets "some JSON". In old-school java world you'd have this mapped into a bean and you'd at least know what you work on.
Previous project I worked on was an inherited poor JS codebase with lots of unrefactorable magic happening inside, multiple event buses and what not. Since JS is a very dynamic and very permissive language, someone can abuse some language constructs to make it very hard to reason about the code.
In a pathological extreme you may get "everything is an object" codebase but no idea what keys each object has.
Bonus points if the code is passing some JSON around and adding some keys to that object in an ad-hoc manner, scattered along many functions.
No tests and no mocks meant that to learn that, you'd have to run the app and put breakpoints. But, the same variable might have had totally different type of data inside depending on how some if-elses executed.
Most of our bugs were due to some objects not having some keys (sometimes) for some reason etc.
- EvanPlaice 11y agoSo, if you want to slice off an object and name it just use ES6 classes. class NameMe { constructor (obj) { for (var prop in obj) this[prop] = obj[prop]; } } This literally just spits out a named object with an identical structure to the object it's being created with. If you want to make all keys visible and guarantee no exceptions are thrown when a key isn't set, just define default values. class NameMe { string = ''; number = 0; bool = false; array = []; object = {}; constructor (obj) { for (var prop in obj) this[prop] = obj[prop]; } } You could always gasp initialize everything to null if you're concerned about the defaults screwing up application logic. That's how static type systems address initialization by default anyway. Want to enforce specific types? Add setter methods that include validation checks. Do I need to continue? None of these characteristics are difficult to define in Javascript. Structuring data in a manner that's easy to reason about is no more difficult in Javascript than it is in any typed language. Even before the introduction of classes you could achieve the same using prototypes. If the code you've encountered was difficult to reason about, it's because the devs who wrote it suck at writing code that's easy to reason about. Self-documenting code is a naming problem not a type theory problem. Any code base, whether static or dynamic should contain an extensive collection of mock data and unit tests to verify common cases and check for breaking edge cases. Unless the project is a one-off fire-and-forget implementation. In which case; who cares, maintenance is somebody else's problem (I'm not stating this is a good ides, just what usually happens in practice). ---- Bonus: How about a couple of cases that 'literally can't even' be done in statically typed OOP languages. Extending existing objects: Sure, you can use object inheritance but that requires explicitly defining a class that represents the new structure. Except that now creates a deeply coupled relationship between the child and parent classes. Which will inevitably become a maintenance issue if the business logic ever changes. Say hello to technical debt. Proof: Why do all of the collection classes in Java inherit from vector? You can accomplish the same in JS in any object (class based or not) using .extend() (provided by most 3rd party frameworks). Which essentially does a deep copy of all the object properties. No deep coupling between objects, no technical debt incurred. Multiple Inheritance: I'm surprised nobody in the OOP community talks about this anymore. Since OOP relationships between objects only point to the external interfaces (ie class definition) rather than reference the underlying data, there needs to be a system to represent those relationships. Multiple inheritance was cast off as not technically feasible due to the possibility of users creating circular references. Ala 'the diamond problem.' The solution. Branch everything off of a central 'god object' and represent all relationships between classes as a directed acyclic graph. This is an artificial constraint that only applies to OOP. That not only makes it unnecessarily painful to reason about the structure of class definitions, but it makes it impossible to pass data to adjacent leaves in the DAG without some form of external global state (ie the singleton pattern). In JS it's very easy to use multiple inheritance. Just create an object and add data from as many other objects as you want using .bind(). Done with an object but still need to maintain it's state? return a new function that maintains a reference to it's enclosing function. Ie use it as a closure. Stop for a second to consider. The .bind() method, which is crucial to adding functional-like aspects to an imperative programming language, is physically not possible in OOP. The closest equivalent is to pass in ref/out params as arguments into the constructor (ie creating more deep links to external classes).