diff options
| author | Ahmed Abdelhalim <[email protected]> | 2025-09-17 12:19:43 +0200 |
|---|---|---|
| committer | Ahmed Abdelhalim <[email protected]> | 2025-09-17 12:19:43 +0200 |
| commit | 326ecfbb3147cc72cccab91f80cc4a1633384aca (patch) | |
| tree | bbc49681f3e164e8fed78c994aef2e4d4cf92c14 /content | |
| parent | fde15d29f006e200ce8509cb6ddf4a66d2bad18f (diff) | |
Add markdown linting and fix some linting issues
Diffstat (limited to 'content')
| -rw-r--r-- | content/blog/exception-handling/index.md | 132 | ||||
| -rw-r--r-- | content/blog/multi-distro-usb.md | 18 | ||||
| -rw-r--r-- | content/blog/slowftware-development.md | 19 |
3 files changed, 88 insertions, 81 deletions
diff --git a/content/blog/exception-handling/index.md b/content/blog/exception-handling/index.md index f6aeda4..ce4d58a 100644 --- a/content/blog/exception-handling/index.md +++ b/content/blog/exception-handling/index.md @@ -181,32 +181,32 @@ def do_some_analysis! end ``` -You might have noticed the error in the previous example; -`return` is misspelled. Although modern editors provide some protection against this specific type of syntax error, -this example illustrates how `rescue Exception` does harm to our code. +You might have noticed the error in the previous example; +`return` is misspelled. Although modern editors provide some protection against this specific type of syntax error, +this example illustrates how `rescue Exception` does harm to our code. At no point is the actual type of the exception (in this case a `NoMethodError`) addressed, or intended to be addressed. ## 3. Never `rescue` more exceptions than you need to -The point 2 is a specific case of this general rule: -We should always be careful not to over-generalize our exception handlers. -The reasons are the same; whenever we catch more exceptions than we should, -we end up hiding parts of the application logic from higher levels of the application, -not to mention suppressing the developer’s ability to handle the exception themselves. +The point 2 is a specific case of this general rule: +We should always be careful not to over-generalize our exception handlers. +The reasons are the same; whenever we catch more exceptions than we should, +we end up hiding parts of the application logic from higher levels of the application, +not to mention suppressing the developer’s ability to handle the exception themselves. **This severely affects the extensibility and maintainability of the code.** -If we do attempt to handle different exception subtypes in the same handler, -we introduce fat code blocks that have too many responsibilities. -For example, if we are building a library that consumes a remote API, handling a `MethodNotAllowedError` (HTTP 405), +If we do attempt to handle different exception subtypes in the same handler, +we introduce fat code blocks that have too many responsibilities. +For example, if we are building a library that consumes a remote API, handling a `MethodNotAllowedError` (HTTP 405), is usually different from handling an `UnauthorizedError` (HTTP 401), even though they are both `ResponseError`s. -As we will see, often there exists a different part of the application +As we will see, often there exists a different part of the application that would be better suited to handle specific exceptions in a more [DRY][7] way. -So, define the [single responsibility][8] of your class or method, -and **handle the bare minimum of exceptions that satisfy this responsibility requirement**. -For example, if a method is responsible for getting stock info from a remote a API, -then it should handle exceptions that arise from getting that info only, +So, define the [single responsibility][8] of your class or method, +and **handle the bare minimum of exceptions that satisfy this responsibility requirement**. +For example, if a method is responsible for getting stock info from a remote a API, +then it should handle exceptions that arise from getting that info only, and leave the handling of the other errors to a different method designed specifically for these responsibilities: ```rb @@ -222,19 +222,19 @@ def get_info end end ``` -Here we defined the contract for this method to only get us the info about the stock. -It **handles endpoint-specific errors**, such as an incomplete or malformed JSON response. -It doesn’t handle the case when authentication fails or expires, or if the stock doesn’t exist. -These are someone else’s responsibility, and are explicitly passed up the call stack +Here we defined the contract for this method to only get us the info about the stock. +It **handles endpoint-specific errors**, such as an incomplete or malformed JSON response. +It doesn’t handle the case when authentication fails or expires, or if the stock doesn’t exist. +These are someone else’s responsibility, and are explicitly passed up the call stack where there should be a better place to handle these errors in a DRY way. ## 4. Resist the urge to handle exceptions immediately -This is the complement to the point 3. -An exception can be handled at any point in the call stack, and any point in the class hierarchy, -so knowing exactly where to handle it can be mystifying. -To solve this issue, many developers opt-in to handle any exception as soon as it arises, -but investing time in thinking this through will usually result +This is the complement to the point 3. +An exception can be handled at any point in the call stack, and any point in the class hierarchy, +so knowing exactly where to handle it can be mystifying. +To solve this issue, many developers opt-in to handle any exception as soon as it arises, +but investing time in thinking this through will usually result in finding a more appropriate place to handle specific exceptions. @@ -253,12 +253,12 @@ def create end end ``` -(Note that although this is not technically an exception handler, functionally, it serves the same purpose, +(Note that although this is not technically an exception handler, functionally, it serves the same purpose, [since `@client.save` only returns `false` when it encounters an exception][12].) -In this case, however, repeating the same error handler in every controller action is the opposite of DRY, -and damages **maintainability** and **extensibility**. -Instead, we can make use of the special nature of exception propagation, +In this case, however, repeating the same error handler in every controller action is the opposite of DRY, +and damages **maintainability** and **extensibility**. +Instead, we can make use of the special nature of exception propagation, and handle them only once, in the [parent controller class][13], `ApplicationController`: ```rb @@ -281,86 +281,86 @@ def render_unprocessable_entity(e) end ``` -This way, we can ensure that all of the `ActiveRecord::RecordInvalid` errors +This way, we can ensure that all of the `ActiveRecord::RecordInvalid` errors are properly and DRY-ly handled in one place, on the base `ApplicationController` level. -This gives us the freedom to fiddle with them if we want to handle specific cases at the lower level, +This gives us the freedom to fiddle with them if we want to handle specific cases at the lower level, or simply let them propagate gracefully. ## 5. Not all exceptions need handling -When developing a gem or a library, many developers will try to encapsulate the functionality -and not allow any exception to propagate out of the library. +When developing a gem or a library, many developers will try to encapsulate the functionality +and not allow any exception to propagate out of the library. But sometimes, it’s not obvious how to handle an exception until the specific application is implemented. -Let’s take [`ActiveRecord`][14] as an example of the ideal solution. -The library provides developers with two approaches for completeness. +Let’s take [`ActiveRecord`][14] as an example of the ideal solution. +The library provides developers with two approaches for completeness. The [`save`][15] method handles exceptions without propagating them, simply returning `false`, -while [`save!`][16] raises an exception when it fails. -This gives developers the option of handling specific error cases differently, +while [`save!`][16] raises an exception when it fails. +This gives developers the option of handling specific error cases differently, or simply handling any failure in a general way. -But what if you don’t have the time or resources to provide such a complete implementation? +But what if you don’t have the time or resources to provide such a complete implementation? In that case, if there is any uncertainty, it is best to **expose the exception**. -Here’s why: We are working with changing requirements almost all the time, -and making the decision that an exception will always be handled in a specific -way might actually harm our implementation, damaging **extensibility** and **maintainability**, +Here’s why: We are working with changing requirements almost all the time, +and making the decision that an exception will always be handled in a specific +way might actually harm our implementation, damaging **extensibility** and **maintainability**, and potentially adding huge technical debt, especially when developing libraries. -Take the earlier example of a stock API consumer fetching stock prices (point 3.). -We chose to handle the incomplete and malformed response on the spot, -and we chose to retry the same request again until we got a valid response. -But later, the requirements might change, such that we must fall back to saved historical stock data, +Take the earlier example of a stock API consumer fetching stock prices (point 3.). +We chose to handle the incomplete and malformed response on the spot, +and we chose to retry the same request again until we got a valid response. +But later, the requirements might change, such that we must fall back to saved historical stock data, instead of retrying the request. -At this point, we will be forced to change the library itself, -updating how this exception is handled, because the dependent projects won’t handle this exception. -(How could they? It was never exposed to them before.) -We will also have to inform the owners of projects that rely on our library. -This might become a nightmare if there are many such projects, +At this point, we will be forced to change the library itself, +updating how this exception is handled, because the dependent projects won’t handle this exception. +(How could they? It was never exposed to them before.) +We will also have to inform the owners of projects that rely on our library. +This might become a nightmare if there are many such projects, since they are likely to have been built on the assumption that this error will be handled in a specific way. Now, we can see, **how this situation degrades the library’s usefulness, extensibility, and flexibility.** So here is the bottom line: **if it is unclear how an exception should be handled, let it propagate gracefully.** -There are many cases where a clear place exists to handle the exception internally, -but there are many other cases where exposing the exception is better. -So before you opt into handling the exception, just give it a second thought. +There are many cases where a clear place exists to handle the exception internally, +but there are many other cases where exposing the exception is better. +So before you opt into handling the exception, just give it a second thought. **A good rule of thumb is to only insist on handling exceptions when you are interacting directly with the end-user.** ## 6. Follow the convention -The implementation of Ruby, and, even more so, Rails, follows some naming conventions, +The implementation of Ruby, and, even more so, Rails, follows some naming conventions, such as distinguishing between `method_names` and `method_names!` with a “bang.” -In Ruby, the bang indicates that the method will alter the object that invoked it, -and in Rails, it means that the method will raise an exception if it fails to execute. +In Ruby, the bang indicates that the method will alter the object that invoked it, +and in Rails, it means that the method will raise an exception if it fails to execute. **Try to respect the same convention, especially if you are going to open-source your library.** -If we were to write a new `method!` with a bang in a Rails application, we must take these conventions into account. +If we were to write a new `method!` with a bang in a Rails application, we must take these conventions into account. There is nothing forcing us to raise an exception when this method fails. -Another Ruby convention, attributed to [Jim Weirich][17] ([Thanks for everything Jim. We will miss you][18]), +Another Ruby convention, attributed to [Jim Weirich][17] ([Thanks for everything Jim. We will miss you][18]), is to use `fail` to indicate method failure, and only to use `raise` if you are re-raising the exception. > An aside, because I use exceptions to indicate failures, -> I almost always use the `fail` keyword rather than the `raise` keyword in Ruby. -> Fail and raise are synonyms so there is no difference except that `fail` -> more clearly communicates that the method has failed. -> The only time I use `raise` is when I am catching an exception and re-raising it, -> because here I’m not failing, but explicitly and purposefully raising an exception. +> I almost always use the `fail` keyword rather than the `raise` keyword in Ruby. +> Fail and raise are synonyms so there is no difference except that `fail` +> more clearly communicates that the method has failed. +> The only time I use `raise` is when I am catching an exception and re-raising it, +> because here I’m not failing, but explicitly and purposefully raising an exception. > This is a stylistic issue I follow, but I doubt many other people do. [Source][19] -Many other language communities have adopted conventions like these around how exceptions are treated, +Many other language communities have adopted conventions like these around how exceptions are treated, and **ignoring these conventions will damage the readability and maintainability of our code.** ## 7. Logger.log(everything) -This practice doesn’t solely apply to exceptions, +This practice doesn’t solely apply to exceptions, but if there’s one thing that should always be logged, it’s an exception. -*Special thanks to [Avdi Grimm][20] and his awesome talk [Exceptional Ruby][21], +*Special thanks to [Avdi Grimm][20] and his awesome talk [Exceptional Ruby][21], which helped a lot in the making of this article.* *This article was originally published with the help of an editor on [Toptal in September 2017][22].* diff --git a/content/blog/multi-distro-usb.md b/content/blog/multi-distro-usb.md index 6993870..fe17602 100644 --- a/content/blog/multi-distro-usb.md +++ b/content/blog/multi-distro-usb.md @@ -12,11 +12,12 @@ or using [ParrotSec][2] for testing or [HTB][3]/[OTW][9] challenges. I knew it was possible to create a hard drive with multiple OS in different partitions, so I thought it had to be possible to do the same thing using a USB drive. - # How to create a multi-distro bootable Linux stick + Unless you are into [GRUB][8] and want to learn more about it and feel more comfortable fixing GRUB issues by yourself, -## Don't. +## Don't + After spending days looking into the grub configurations for running Rocky Linux and Proxmox (to no avail), I'm going to save you a deep rabbit hole dive. @@ -43,6 +44,7 @@ To quote the documentation: [Glim][7] ## Why? + At this point, I can't tell you why an image would work or not. I can't tell you how to boot Rocky Linux or what the required parameters to run Proxmox Linux are. @@ -50,13 +52,15 @@ All I can tell you is that it took me way too long to figure out that it's not s There might be a reason why it fails on the machine with the SSD or why it couldn't insert the `thinkpad_acpi` module, but for the original purpose of having a convenient way of setting up Linux and updating images, I will stick with having to run + ```bash -$ diskutil list -$ diskutil eraseDisk JHFS+ Untitled /dev/diskX -$ diskutil unmountDisk /dev/diskX -$ sudo dd bs=4M if=path/to/image.iso of=/dev/diskX status=progress oflag=sync +diskutil list +diskutil eraseDisk JHFS+ Untitled /dev/diskX +diskutil unmountDisk /dev/diskX +sudo dd bs=4M if=path/to/image.iso of=/dev/diskX status=progress oflag=sync ``` -and waiting for a few minutes, for now. + +And waiting for a few minutes, for now. [1]: https://tails.net/ [2]: https://parrotsec.org/ diff --git a/content/blog/slowftware-development.md b/content/blog/slowftware-development.md index 4995d59..1cd8c7d 100644 --- a/content/blog/slowftware-development.md +++ b/content/blog/slowftware-development.md @@ -5,6 +5,7 @@ draft: false --- # Slow is Fast + The fastest way to create high-quality software is to develop this software slowly and thoughtfully. It might seem counterintuitive, but by focusing on understanding the problem, designing the **right** solution, and refining it through iteration and simplification, @@ -14,6 +15,7 @@ Rushing to get an MVP out the door often results in poorly scoped projects and a technical debt, and "Big balls of Mud" -- architectures that are costly to maintain, and slow to fix. # Perfection Through Removal + > "Perfection is attained, not when there is nothing more to add, but when there is nothing more to remove." <br> > --- _Antoine de Saint-Exupéry’s observation_ @@ -22,6 +24,7 @@ Clean, maintainable software is born from stripping down, not piling on. Yet, si It requires careful thinking, planning, and understanding the problem before jumping into coding. # Worse is Better + In 1989, Professor [Richard P. Gabriel][4] wrote an [essay][5] to describe the dynamics of software acceptance. It is the argument that software quality does not necessarily increase with functionality: that there is a point where less functionality ("worse") is a preferable option ("better") in terms of practicality and usability. @@ -49,6 +52,7 @@ By aiming for simplicity, you not only make the system easier to work with but a improvements. # Working Backwards + Amazon’s "[Working Backwards][21]" process exemplifies the power of taking time upfront to define the customer experience. Before any code is written, teams work backward from the final product, crafting press releases and FAQs to clarify the vision. This approach forces developers and stakeholders to deeply @@ -58,6 +62,7 @@ By focusing on the end goal from the start, it ensures that despite that the pro and slower, it actually speeds up the development and delivers better products.[^2][^6] # Agile Pitfalls + Agile, though powerful, is often misunderstood and misinterpreted, Many organizations are taking it too far and using it to avoid careful planning and preparation[^3], Many teams focus on velocity and delivering a constant stream of features without truly understanding the product’s requirements. @@ -66,6 +71,7 @@ Instead of rushing to build the next feature, teams should balance speed with pr decisions, focusing on building sustainable solutions rather than quick fixes. # Avoid the Big Ball of Mud + In a [paper][10] published in 1997, Authors [Brian Foote][11], and [Joseph Yoder][12] examine the most frequently deployed architecture, dubbed "The BIG BALL OF MUD": > A BIG BALL OF MUD is a casually, @@ -94,6 +100,7 @@ authors: Good architecture takes time and meticulous thought, but it pays off when your system is easy to maintain and scale. # Code Disposability Over Reusability + Another shift in thinking comes with focusing on code disposability. Rather than trying to write code that’s reusable across the board, aim for code that’s easy to delete or replace. @@ -110,6 +117,7 @@ In such cases, a "healthy" level of repetition that would make the code more rep making the code more reusable (by being more "DRY").[^5] # Slowftware Development + (or slow software development), is the collection of these old ideas and concepts, to develop valuable, maintainable software with patience and a deliberate focus on simplicity and correctness. @@ -126,6 +134,7 @@ Special thanks to [Abdallah Dorra][34] and [Tom Nick][35] for proofreading, revi this post. Further Readings: + - [Write code that is easy to delete, not easy to extend.][26] - [Write code that’s easy to delete, and easy to debug too.][27] - [Repeat yourself, do more than one thing, and rewrite everything.][28] @@ -147,11 +156,8 @@ Further Readings: [^2]: The Kindle e-reader, AWS cloud computing services, and the Echo voice assistant with Alexa all came from "working backwards" at Amazon. -[^6]: Fun fact: The initial [version][38] of this article was written in an "investigative journalistic" style - about the history of the blog's ideas and philosophies, but [Tom Nick][35] suggested "working backwards" - from the desired conclusion and end goal of the post, and building the narrative from there, - removing all the unnecessary narrative, quotes, and undefended claims. Which led to a better and shorter - final version. +[^6]: Fun fact: [Tom Nick][35] suggested "working backwards" from the initial longer version, to make it direct and + concise, instead of a long story told. [^3]: "The fundamental problem with agile, as many companies use it, is that its relentless pace biases developers. They want to get out a minimum viable product in only a few weeks, so they skimp on scoping out just what the @@ -172,8 +178,6 @@ Further Readings: [11]: http://www.laputan.org/ [12]: https://joeyoder.com/ [18]: https://hbr.org/2021/04/have-we-taken-agile-too-far -[19]: https://www.amazon.com/stores/author/B08FRTFQD8/about -[20]: https://www.amazon.com/stores/author/B08HW23WY6/about [21]: https://www.amazon.com/Working-Backwards-Insights-Stories-Secrets/dp/1250267595 [26]: https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to [27]: https://programmingisterrible.com/post/173883533613/code-to-debug @@ -185,7 +189,6 @@ Further Readings: [35]: https://tn1ck.com/ [36]: https://sandimetz.com/about [37]: https://www.youtube.com/watch?v=8bZh5LMaSmE -[38]: https://git.sr.ht/~a14m/a14m.srht.site/tree/9176ade8ff48b3f4d1e538a572bf538c55b480c7/item/content/blog/slowftware-development.md [40]: https://hbr.org/2010/05/need-speed-slow-down [41]: https://go.dev/blog/compat [42]: https://github.com/zakirullin/cognitive-load |
