Skip to content

Commit 527bee8

Browse files
committed
feat: crudl and article
1 parent 1c4e891 commit 527bee8

File tree

6 files changed

+236
-2
lines changed

6 files changed

+236
-2
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"peacock.color": "#34570e",
1818
"cSpell.words": [
19+
"Crudl",
1920
"micromark",
2021
"slipnslide",
2122
"Swapi",

articles/dry-kiss.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Dry kiss Yagni ASAP.
2+
3+
When you where lured here by the title and was expecting some story about why someone would to dry kiss this Yagni person, I'm sorry, the following will be probably boring for you.
4+
If you came here because you recognized the title as having some interesting keywords. Congratulations, you are into coding, and you came to the right place.
5+
When you recognized the title consists only out of principle abbreviations, I'm sorry too ;) you are probably a bit too much into coding.
6+
7+
By now you probably understand this this article is going to be about coding. It is indeed. But first things first, lets un-abbreviate the title.
8+
9+
- DRY: Don't Repeat Yourself
10+
- KISS: Keep It Stupid Simple (or super simple, or short and simple, or ...)
11+
- YAGNI: You Ain't Gonna Need It
12+
- ASAP: As Simple As Possible
13+
14+
So, with that out of the way, lets ask us the most important question: Why would I combine these four principles in one title?
15+
Well first of all, because I can. It came to me in a dream. Well, not actually, but I found it amusing enough to create an article about it.
16+
Secondly, because those four principles are very important for coding. I'm not going to say they are the _most_ important, because I would like to leave that up to you. But they are a solid(pun intended) way to keep your sanity while coding.
17+
18+
Enough with the introduction, let me start with high level explanation of the four principles. (I'm going to assume you know them, if not, please look them up, there are plenty of resources available)
19+
20+
## The four principles:
21+
22+
### DRY
23+
24+
This is about not repeating (copy-pasting) code. When you need to do the same thing in multiple places, you should abstract this away into a function, class, macro, decorator, whaterver your language supports. This way, when you need to change the behavior, you only need to change it in one place.
25+
26+
### KISS
27+
28+
This is about keeping readability. When you write code, you should always keep in mind that someone else is going to read it. This can be a colleague, a future you. The more readable your code is, the easier it is to understand, and the less likely it is to contain bugs. The simple is about 'simple' as in 'easy to understand', not 'simple' as in 'easy to write'.
29+
30+
### YAGNI
31+
32+
This is about not over-engineering. When you write code, you should always keep in mind that you are writing it for a purpose. You should only write the code that you need right now, not the code that you might need in the future. Only construct for the _current_ requirements. We tend to be very bad at predicting the future. So, don't try to predict it, just write the code that you need right now. If you need it in the future, you can always add it later.
33+
34+
### ASAP
35+
36+
This is about keeping it simple. But not entirely the same as KISS. The 'As Possible' part is operative here. This is a warning against under-engineering.
37+
38+
## Whats in common between them?
39+
40+
All four principles are about keeping your code maintainable, readable, and understandable. They are about keeping your code bug-free, and keeping your code future-proof. They are about the quality of your code in good shape. They are telling you to be friendly to the person that has to maintain the code in the future. Keep in mind that this person might be you. But there is _no_ guarantee that this person is you. So, be nice to them.
41+
42+
## But, are they not contradicting?
43+
44+
Yes, No, probably.
45+
46+
Euhm, that's probably not helpful. The thing is, it is about finding balance. You should not over-engineer, but you should not under-engineer either. You should not repeat yourself, but you should not abstract everything away. You should keep it simple, but you should not make it too simple. You should only write the code that you need right now, but still make sure it is written in a way that it can be extended in the future.
47+
this all is _not_ simple. It is a balancing act. With experience you will get better at it. But you will never be perfect at it. And that is okay. As long as you keep trying to get better at it.
48+
49+
## HELP! I'm lost!
50+
51+
Don't worry. You are not alone. We are all lost. But let's try to get at least a bit less lost. Here are some tips to help you on your way:
52+
53+
### DRY
54+
55+
When you find yourself copy-pasting code, stop. Is this the first time? A second copy for something similar, with only a little difference isn't a real problem. You are OK.
56+
No difference, but only a different "input"? Abstract it away.
57+
No difference, but a very different context? probably not a good idea to abstract it away. But keep it in mind, maybe you will find a way to abstract it away later.
58+
But when you find yourself copy-pasting code for the third time, you should start thinking about pitting this in a reusable unit?
59+
60+
> [!NOTE] Reusable Unit:
61+
> This means a function, a Class, a Macro, a Decorator, a Template, a Partial, a Component, a Module, a Package, a Library, a Framework, a Service, a Microservice, a Plugin, a Middleware, a Hook, a Mixin, a Trait, a Aspect, a ... you get the idea. There are many ways to abstract code away. Choose the one that fits your language and your problem best.
62+
63+
### KISS
64+
65+
Look at your code. Are there many brackets? Is it deeply nested? Are there multiple exit points? Does it update things that are not in the same scope(aka side-effects)? Make the variable names sense? _Do they?_ Is my unit larger than fits my screen? Look at the amount of conditionals. See if it makes sense to split it out in smaller pieces.
66+
67+
### YAGNI
68+
69+
When you are writing code, and you think: "I might need this in the future", stop. Ask yourself: "Do I need this now?" If the answer is no, don't write it. If the answer is yes, write it. But keep in mind that you are writing it for the current requirements. If you need it in the future, you can always add it later. But you might not need it in the future. So, don't write it now. You Ain't Gonna Need It.
70+
71+
> [!TIP] your 85% sure you are going to need it in the future?
72+
> Write it down in a TODO. But don't write the code. Just write down what you think you are going to need. This way, you can always look it up later. And you can always decide to write it later. But you might not need it at all. So, don't write it now.
73+
74+
> [!TIP] PRO-TIP
75+
> your 98% sure you are going to need it in the very near future? **write it!**
76+
77+
78+
### ASAP
79+
80+
Look at your unit. Check for KISS, Check for DRY, Check for YAGNI. If you are OK with all of them, you are probably OK with ASAP. But keep in mind that ASAP is about keeping it simple. _as possible_. So, are you sure _all_ edge-cases are dealt with? Are _all_ requirements indeed met?
81+
82+
## Conclusion
83+
84+
Those four principles will help you to fight the complexity of your day to day job. Keeping them in mind will help code quality. But remember, just as with anything else (eyeballing "best practices" especially here!), that those are guidelines. It is oke to violate them. Make sure there is a just cause for it though. And if you do, strongly consider documenting in a code-comment why you did it. This way, the person that has to maintain your code in the future, will at least have a fighting chance to understand why you did it.
85+
86+
```javascript
87+
// not DRY: performance reasons.
88+
89+
// not DRY: time restrictions.
90+
91+
// not KISS: a bug in the library I'm using.
92+
```
93+
94+
Are the kind of comments one would like to see. The last one could be a bit more specific, but you get the idea.
95+
96+
## final thoughts.
97+
98+
Let me quote Antoine de Saint-Exupéry
99+
> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
100+
101+
This _is_ the essence of the four principles. He was talking about design. But it is even more true (yes, there are gradations in truth!) for code. It actually means you are doing the job with the most optimal amount of code. And in most cases that will also means the best possible performance. (There is a whole slew of other things that can influence performance, but that is a whole other article)
102+
103+
So, when you are going back to code, make sure you are going to:
104+
105+
`Dry kiss yagni asap!`

package-lock.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@
188188
"@fortawesome/free-regular-svg-icons": "^6.7.2",
189189
"@fortawesome/free-solid-svg-icons": "^6.7.2",
190190
"@ngneat/falso": "^7.3.0",
191+
"@std/front-matter": "npm:@jsr/std__front-matter@^1.0.5",
192+
"@std/toml": "npm:@jsr/std__toml@^1.0.2",
191193
"@toast-ui/editor": "^3.2.2",
192194
"animate-css-grid": "^1.5.1",
193195
"bson": "^6.10.1",

src/app/filter-samp/filter-sample/filter-sample.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AsyncPipe } from '@angular/common';
22
import { Component, inject } from '@angular/core';
33
import { ReactiveFormsModule, UntypedFormControl, ɵInternalFormsSharedModule } from '@angular/forms';
4-
import { combineLatest } from 'rxjs';
5-
import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators';
4+
import { combineLatest, of } from 'rxjs';
5+
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
66

77
import { AddressService, UserCard } from '../../generic-services/address.service';
88

src/utils/crud/crud.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { HttpClient, HttpParams } from '@angular/common/http';
2+
import { inject } from '@angular/core';
3+
import { firstValueFrom } from 'rxjs';
4+
5+
export function injectCrudlClient<T = Record<string, unknown>>(baseUrl: string) {
6+
const http = inject(HttpClient);
7+
return new CrudlClient<T>(baseUrl, http);
8+
}
9+
10+
class CrudlClient<T> {
11+
constructor(
12+
private readonly baseUrl: string,
13+
private readonly http: HttpClient
14+
) {}
15+
16+
async create(data: T) {
17+
try {
18+
const result = await firstValueFrom(this.http.post<T>(this.baseUrl, data));
19+
return result;
20+
} catch (error) {
21+
// for now, we probably want to rethrow the original error
22+
throw new Error('Failed to create data');
23+
}
24+
}
25+
26+
async read(id: string) {
27+
try {
28+
const result = await firstValueFrom(this.http.get<T>(`${this.baseUrl}/${id}`));
29+
return result;
30+
} catch (error) {
31+
throw new Error('Failed to read data');
32+
}
33+
}
34+
35+
async update(id: string, data: T) {
36+
try {
37+
const result = await firstValueFrom(this.http.put<T>(`${this.baseUrl}/${id}`, data));
38+
return result;
39+
} catch (error) {
40+
throw new Error('Failed to update data');
41+
}
42+
}
43+
44+
async delete(id: string) {
45+
try {
46+
await firstValueFrom(this.http.delete(`${this.baseUrl}/${id}`));
47+
} catch (error) {
48+
throw new Error('Failed to delete data');
49+
}
50+
}
51+
52+
async readAll() {
53+
try {
54+
const result = await firstValueFrom(this.http.get<T[]>(this.baseUrl));
55+
return result;
56+
} catch (error) {
57+
throw new Error('Failed to read all data');
58+
}
59+
}
60+
61+
async list(constraints: Partial<Constrains>) {
62+
try {
63+
const params = new HttpParams({ fromObject: constraints as any });
64+
// we probably want to add a real check and conversion from constraints to HttpParams, and put in sane defaults
65+
const result = await firstValueFrom(this.http.get<Id[]>(this.baseUrl, { params }));
66+
return result;
67+
} catch (error) {
68+
throw new Error('Failed to list data');
69+
}
70+
}
71+
}
72+
73+
type FieldName = string;
74+
type Id = string;
75+
interface Filter {
76+
field: FieldName;
77+
value: string;
78+
operator: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'like' | 'in' | 'startsWith' | 'endsWith';
79+
}
80+
interface Constrains {
81+
order: 'asc' | 'desc';
82+
limit: number;
83+
offset: number;
84+
pageSize: number;
85+
filter: Filter[];
86+
sortBy: FieldName[];
87+
}

0 commit comments

Comments
 (0)