This ifis compounded further when you consider that methods generally return one value (unless language specific features exist to return more values), and things like a location would want you to return two values (lat + lon). This incentivizes you to create a Location class to simplify your code.
This if compounded further when you consider that methods generally return one value (unless language specific features exist to return more values), and things like a location would want you to return two values (lat + lon). This incentivizes you to create a Location class to simplify your code.
This is compounded further when you consider that methods generally return one value (unless language specific features exist to return more values), and things like a location would want you to return two values (lat + lon). This incentivizes you to create a Location class to simplify your code.
The top answers are already good, but I wanted to address some of your questions directly.
Is unit testing necessary for writing smaller pieces of code?
The size of the code is not directly related to the need for unit tests. It is related indirectly: unit tests are more valuable in complex codebases, and small codebases are generally not as complex as larger ones.
Unit tests shine for code where it's easy to make mistakes, or when you're going to have many implementations of this code. Unit tests do little to help you with current development, but they do a lot to prevent you making mistakes in the future that cause existing code to suddenly misbehave (even though you didn't touch that thing).
Let's say you have an application where Library A performs the squaring of numbers, and Library B applies the Pythagorean theorem. Obviously, B depends on A. You need to fix something in library A, and let's say you introduce a bug that cubes numbers instead of squaring them.
Library B will suddenly start misbehaving, possibly throwing exceptions or simply giving wrong output. And when you look at the version history of library B, you see that it is untouched. The problematic end result is that you have no indication of what could be going wrong, and you're going to have to debug the behavior of B before you realize the problem is in A. That's wasted effort.
Enter unit tests. These tests confirm that library A is working as intended. If you introduce a bug in library A that causes it to return bad results, then your unit tests will catch that. Therefore, you won't be stuck trying to debug library B.
This is beyond your scope, but in a continuous integration development, unit tests are executed whenever someone commits some code, which means you'll know you broke something ASAP.
Especially for complicated mathematical operations, unit tests can be a blessing. You do a few example calculations, and then you write unit tests which compared your calculated output and your actual output (based on the same input parameters).
However, note that unit tests won't help you create good code, but rather maintain it. If you usually write code once and never revisit it, unit tests are going to be less beneficial.
How about OOP?
OOP is a way of thinking about distinct entities, for example:
When a
Customerwants to purchase aProduct, he talks to theVendorto receive anOrder. TheAccountantwill then pay theVendor.
Compare this to how a functional programmer thinks about things:
When a customer wants to
purchaseProduct(), hetalktoVendor()so they willsendOrder()to him. The acccountant will thenpayVendor().
Apples and oranges. Neither of them is objectively better than the other. One interesting thing to note is that for OOP, Vendor is mentioned twice but it refers to the same thing. However, for functional programming, talktoVendor() and payVendor() are two separate things.
This showcases the difference between the approaches. If there is a lot of shared vendor-specific logic between these two actions, then OOP help reduce code duplication. However, if there is no shared logic between the two, then merging them into a single Vendor is futile work (and therefore fuctional programming is more efficient).
More often than not, mathematical and scientific calculations are distinct operations that do not rely on implicit shared logic/formulae. Because of that, functional programming is more often used than OOP.
What sorts of approaches are good for writing good, clean code quickly when doing "scientific programming" as opposed to working on larger projects?
Your question implies that the definition of "good, clean code" changes whether you're doing scientific programming or working on larger (I assume you mean enterprise) projects.
The definition of good code does not change. The need to avoid complexity (which can be done by writing clean code), however, does change.
The same argument comes back here.
- If you never revisit old code and fully understand the logic without needing to compartmentalize it, then don't spend excessive effort to make things maintainable.
- If you do revisit old code, or the required logic is too complex for you to tackle all at once (thus requiring you to compartmentalize the solutions), then focus on writing clean, reusable close.
I ask these questions because often, the programming itself isn't super complex. It's more about the math or science that I'm testing or researching with the programming.
I get the distinction you're making here, but when you look back at existing code, you are looking at both the math and the programming. If either is contrived or complex, then you'll struggle to read it.
E.g., is a class necessary when two variables and a function could probably take care of it?
OOP principles aside, the main reason I write classes to house a few data values is because it simplifies declaring method parameters and return values. For example, if I have a lot of methods that use a location (lat/lon pair), then I will quickly tire of having to type float latitude, float longitude and will much prefer to write Location loc.
This if compounded further when you consider that methods generally return one value (unless language specific features exist to return more values), and things like a location would want you to return two values (lat + lon). This incentivizes you to create a Location class to simplify your code.
E.g., is a class necessary when two variables and a function could probably take care of it?
Another interesting thing to note is that you can use OOP without mixing data values and methods. Not every developer agrees here (some call it an antipattern), but you can have anemic data models where you have separate data classes (stores value fields) and logic classes (stores methods).
This is, of course, on a spectrum. You don't need to be perfectly anemic, yu can use it when you consider it appropriate.
For example, a method that simply concatenates the first and last name of a person can still be housed in the Person class itself, because it's not really "logic" but rather a calculated value.
(Consider these are also generally situations where the program's speed is preferred to be on the faster end - when you're running 25,000,000+ time steps of a simulation, you kinda want it to be.)
A class is always as big as the sum of its fields. Taking the example of Location again, which consists of two float values, it's important to note here that a single Location object will take up as much memory as two separate float values.
In that sense, it doesn't matter whether you're using OOP or not. The memory footprint is the same.
Performance itself is also not a big hurdle to cross. The difference between e.g. using a global method or a class method has nothing to do with runtime performance, but has everything to do with compile-time generation of bytecode.
Think of it this way: whether I write my cake recipe in English or Spanish doesn't change the fact that the cake will take 30 minutes to bake (= runtime performance). The only thing that the recipe's language changes is how the cook mixes the ingredients (= compiling the bytecode).
For Python specifically, you don't need to explicitly pre-compile the code before calling it. However, when you don't pre-compile, the compilation will occur when trying to execute the code. When I say "runtime", I mean the execution itself, not the compilation which could preceed the execution.