-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I added my comments to the guidelines (only). #2
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
### What do I think about you and your experience. | ||
You hopefully know by know, that I like your attitude and that I admire the effort you put in your thoughts and writing. And I like the outcome of that and that it does make sense. | ||
|
||
But, also quite frankly, it seems to me that you are young and will have more experiences throughout your life. You will grow — as I have done, but hopefully with another end result (otherwise, how would humanity grow, if we only were to repeat ourselves). | ||
|
||
Given your perspective and assumed experience, what you write is a logical conclusion and does make total sense. On the other hand you are not a first with your line of thought about "Flows". Functional decomposition was given as an example of my Professor of a bad architecture, because — you guess it — of its repetition in the 1990ies. The patterns you will find are simple, if that is your *only* guideline. So, what it tells you about the structure of your code is little. | ||
|
||
### Hard to understand code. Why must it be so complicated? | ||
|
||
I assume, you have made the experience, that code can be hard to understand. When trying to understand the code someone else wrote, you have to dive down into their levels of abstraction, looking for and understanding data and data structures which are not natural to the problem at hand. | ||
|
||
Can't we just write code, which follows the natural flow of things? Which describe the feature/problem at hand in the problem domain? These are natural question and you found the write answers. | ||
|
||
The unsolved problem is *when* to follow The-River and when to use abstraction and, if so, to which level? | ||
|
||
### Your personal experience | ||
|
||
I guess and assume a lot about you as a person and about your experience. I should be all wrong, because I know rather nothing about you: | ||
|
||
* Did you work on large projects without being in control? Control means: Being a moderator or the main contributor. | ||
* Have you worked in a team — as opposed to "with others"? | ||
|
||
Through such experiences you might learn that *your* understand of "natural" if different from what others consider "natural". Neither of you is right or wrong. | ||
|
||
### My personal experience | ||
|
||
I did bad decomposition / abstraction in the past and experienced it from others, see below. I can support your assumptions on wrong abstraction. That is, why I believe, it makes sense for others to read your thoughts and learn from them. | ||
|
||
#### Strict rule in my teens | ||
|
||
To avoid repeating I came up with the following rule: "If three statements are the same in two different functions, then create a new function and call it instead." | ||
|
||
This of course is rubbish and way to strict, but it is where the general do-not-repeat-yourself statement lead me. I do not know, if I actually stuck to the rule eventually; hopefully not. | ||
|
||
#### Weird obedience to the "Short-Function" rule. | ||
|
||
A member of my team was not a very good programmer. I had to review his code and provide him with feedback. Our code-checker at the time reported lengthy functions. As it was stupid it just counted lines and told you: "That function is 250 lines > 70 lines". | ||
|
||
His member functions were way longer and did touch different levels of hierarchy/abstraction. I assume, they would have broken your River-Guidelines as well. But, he did not move the stuff on the "lower" levels into private function to be called by the original function. That way, the original function would still show the whole flow, but on a "higher" level. | ||
|
||
Instead he just cut the function in half. So the first half remained in the original function and the second half would move into a new function. Unfortunately the context of the first half needed to be passed ot the second half, so he moved all of that into member fields. Therefore, the class abstraction also became completely obscured. | ||
|
||
I am not telling this, to bash on that colleague, but to support your claim that existing rules can be misinterpreted, and bending them for good reason does make sense. I consider your thinking a good reason. | ||
|
||
#### How we wanted to use flow in my domain of work | ||
|
||
I am working in diagnostics of vehicle electronics. 60-80 micro controllers (ECUs) are installed in a single vehicle and most of them need to be configured, checked, updated and at some point in time probably replaced. If you support multiple vehicle models you have multiple such setups. A successor model will have carry-over ECUs which are similar, but not necessarily identical to the predecessor model. | ||
|
||
Many of these things are very specific flows. Each flow is mostly independent of others. Specifications for such operations are provided as flow charts. I believe The-River fits perfectly. | ||
|
||
The company developed a domain specific language which should allow the people who work for manufacturing or after sales (work shops) create those flows quickly. One group of authors (developers) is usually responsible for a specific vehicle model or subset of those ECUs. They do not want any change by other teams influence their work, so they somehow try to avoid reuse. The language is a graphical flow-chart language. | ||
|
||
The idea was to create new scripts quickly whenever you need a new sequence to handle some requirement. Quickly adapt if a change is required to a local script. Adding a little to a local sequence would just require a little change to the implementing script. Surprises should have been avoided, that adding little functionality would cause much higher efforts than the original change, because of implied refactoring. | ||
|
||
I believe to this day, that supporting The-River is the right thing to do there. | ||
|
||
We failed. But that is not an argument against The-River, but an example of not pursuing it hard enough. Unfortunately it is also an example, why we need other options as guidelines. | ||
|
||
Why did we fail: | ||
* The tool was not fast enough. Creating a script was not a thing of seconds. | ||
* The base layer's were not powerful enough. In your example you call `parsed_item = parse_item(item)`. If you have no `parse_item` function, you create your own script. | ||
* We asked more from the tool than it was designed for. Instead of creating scripts for each ECU, we created general reusable script that implemented the same thing for multiple ECUs. This resulted in parameterization, concurrency and a complexity that the scripting language could not handle well. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,30 @@ | ||
# The River: Core Guidelines for Software Development | ||
|
||
> I mark my insertions and comments as quotes. Which is unfortunately a somewhat inverse usage of the mark up. | ||
This is not meant as a start of a discussion, but as feedback on the text. I avoided questions. Do with this feedback as you wish: Ignore it, think about it, be convinced by it, understand it as being out of scope. It is up to you. Your essay and these guidelines clearly show, that you not only have thought about this in depth, but also invested a lot of effort into expressing your thoughts. | ||
|
||
## Fundamental Principles | ||
|
||
### 1. Flow-Based Organization | ||
Code should be organized around complete flows of functionality rather than arbitrary technical boundaries. Each significant feature should be treated as its own "river", a coherent path from beginning to end. The goal is to maintain locality, visibility and comprehensibility of the entire process. | ||
|
||
> If those technical boundaries are indeed *arbitrary* I fully agree. Often they are required by the techniques used. If those are strictly necessary to achieve the goal is another discussion. | ||
|
||
### 2. Complete Responsibility | ||
Functions should aim to handle their entire responsibility from start to finish, regardless of length. Rather than fragmenting logic across multiple small functions, allow each function to tell its complete story. The key metric is not size but clarity, isolation and coherence of flow. | ||
|
||
> Fully agree | ||
|
||
### 3. Natural Boundaries | ||
Systems should be divided along natural feature boundaries, like a watershed contains multiple rivers. Each feature flow should maintain its independence while connecting with others at well-defined points. These boundaries should emerge from the problem domain rather than being imposed by technical considerations. | ||
|
||
> Feature flow is just one aspect on how do decompose/structure/model a complex system. Computer Science has to deal with the challenge to adhere to multiple decompositions at once. That is why aspect-oriented programming became a thing and why we move elements (realization of concepts) from code to annotations. | ||
|
||
> You speak of *well-defined points* of interactions. Research will have to spend time, and experience will show what make a point *well-defined* with regard to feature flow. | ||
|
||
> I agree that staying close to the problem domain is a favorable goal. I believe object orientation started to address that challenge. I rather see `Student` as a class name than `IMainDBObject`. | ||
One kind of abstraction (My [professor](https://www.se-rwth.de/staff/Manfred.Nagl/) identified three kinds, which I no longer remember) leads from the problem domain (very abstract) to the computer implementation (less abstract). Once the implementation is on those less abstract layers the line between the modeled problem domain and the computer domain blurs. | ||
|
||
## Implementation Guidelines | ||
|
||
### Code Structure | ||
|
@@ -20,28 +34,58 @@ Systems should be divided along natural feature boundaries, like a watershed con | |
4. Use clear section comments to guide readers through longer flows | ||
5. Keep feature flows independent of each other where possible | ||
|
||
> If *flow of a feature* is the only or main principle of structure, I fully agree. But, as I hopefully have commented in the main essay, I believe other principles are needed as well. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe your reply on this revers to the River-Guidelines yourself and that my comment means that is it not yet complete and should be extended. My comment addresses something else. The whole "The River" approach assumes a certain main principle of structure. If I draw the program it will always be a flow-chart. My experience differs. In some applications the flow-chart is a good choice and the The-River is a good principle. It then makes sense to follow your guidelines. But on other occasions other models are better suited to solve the problem. I have yet to elaborate on that and what other structures I mean. |
||
### Abstraction Rules | ||
1. Prefer local abstractions that serve immediate context over global utilities | ||
2. Allow patterns to emerge naturally rather than creating abstractions upfront | ||
|
||
> This is so true! I saw over-engineered code which added interfaces to every class or using a visitor pattern with only one visitor and one very concrete collection to traverse. | ||
|
||
3. Abstract only when it genuinely improves understanding, performance or maintainability. And do it only after reflecting on the potential cost vs benefit of doing so. | ||
|
||
> I believe the before mentioned visitor pattern was included to *improve* readability. If you are best friends with certain patterns and all other team members are as well, then using such patterns might indeed improve readability. At the time, at least one team member (me) found it way harder to understand. | ||
|
||
4. Keep abstractions close to where they're used, preserving context | ||
5. Accept similar patterns in different contexts rather than forcing reuse | ||
|
||
> *Forcing* reuse does not make sense, agreed. If you scale the complexity of the problem domain, you will encounter the true *need* for reuse, because you will have to check 99 flows, if they also contain the bug you just fixed in 1 of 100 flows (I am aware that there are draw-backs on the reuse which might be harder to handle). | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I accept your reply. But, take a change in requirement. The resolution of the screen in use will change and those flows do contain actual pixel numbers (which could have been avoided in the first place, but have not). One flow contains: First column (100px) second column (150px) third column (remaining). When 500 pixels were available this is just fine. Now 1000 pixels are available. and I have to double those numbers. Contra Flow: How lovely it would have been if they all used a single shared parameterized screen, so I only had to change that one screen. Pro Flow: How aweful it would have been if I had only a single shared parameterized screen to handle the requirements of 100 different flows. No one will understand the resulting parameters and any change to the screen might break many flows. I had to deal with such a change for the Porsche Workshoptester. |
||
### Error Handling | ||
1. Handle errors within the flow where they occur | ||
|
||
> In my experience error handling is hard. This is partially due to functional decomposition and abstraction, but will remain true if I have to handle an error after the river split up; if I have to pass the error over through a "well-defined point of connection" between flows. | ||
|
||
2. Provide rich context in error messages by leveraging local knowledge | ||
3. Design flows to contain and isolate failures | ||
4. Make error states and recovery paths visible within the main flow | ||
|
||
> I think the last two points have not been shown/discussed in the essay. If we were to discuss them, examples of what that means for you were required. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes |
||
### Cross-Cutting Concerns | ||
1. Treat logging, security, and monitoring as part of the local environment | ||
|
||
> This might hide the flow. How do you see/mitigate that? | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I mean: The flow in one of your examples is:
If you add logging, assertions, more checks for security and monitoring additions all this cross cutting concers will clutter up the function and hide the flow. I might not see the actual parsing in the code, because most of the code after the |
||
2. Allow each flow to handle cross-cutting concerns in context-appropriate ways | ||
|
||
> Nice, but I have *no* idea how to achieve that. | ||
|
||
3. Accept that similar patterns may repeat across different flows | ||
4. Focus on clarity and completeness within each flow rather than global consistency | ||
|
||
> Agreed, but: Global consistency can in turn increase readability across team members and thus maintainability. Following some idioms is ok. | ||
If global consistency is required (e.g. You have to do C before A before B) to make things work, maintenance becomes hard (because people will "forget" to do C or A). Avoiding such global consistency completely, might impose abstraction (I hide doing C and A, from whoever does B) and might obscure the flow. Where to settle for compromise between those extremes is a matter of actual situation. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you did not understand my comment. Or I did not understand your reply. Let me try to explain again. To make things work I have to do C before A before B. If I understand the flow-principle I will have multiple functions which read:
Maybe even with some flow specific stuff inbetween. I could introduce a functional abstraction: A function that does the above, which I call in every flow. Problem solved. But I have broken other flow-principles. The error handler is now further away from the context that it aims to report. I no longer have the freedom to move flow specific stuff inbetween those calls to A or B. If it is already a well known fact to everyone on the team that of course you have to |
||
### Growth and Scaling | ||
1. Add new capabilities as new rivers rather than complicating existing ones | ||
|
||
> Good and important point. I might otherwise believe to follow the above guidelines by adding some if-statements to an existing flow. | ||
|
||
2. Maintain constant complexity by isolating it within each flow while the system grows | ||
|
||
> I am not sure I understand: Does it mean all the statements in flow should be on the same level of abstraction, e.g. the problem domain? | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What would I see in a function which does need other parts of the system? I find it hard to distinguish good from bad functions by that criteria. |
||
3. Allow each flow to evolve independently based on its specific needs | ||
4. Focus on isolation and fault containment between flows | ||
|
||
|
@@ -58,22 +102,41 @@ Systems should be divided along natural feature boundaries, like a watershed con | |
1. Can you understand the complete flow without jumping between files? | ||
2. Are abstractions serving the immediate context or forcing premature generalization? | ||
3. Is similar code truly duplicate, or serving different contexts? | ||
|
||
> Rather: Do unified code segments server readability or just avoid code duplication? | ||
It seems to me you assume, that the reviewer found code duplication and likes to criticize it. | ||
|
||
4. Are boundaries between flows natural and appropriate? | ||
5. Does the code tell a clear, coherent story? | ||
|
||
## Anti-Patterns to Avoid | ||
|
||
1. Breaking flows into small functions - unless with clear and worthwhile benefits | ||
2. Creating global abstractions - unless absolutly necessary | ||
3. Forcing reuse between unrelated contexts - unless absolutly needed | ||
2. Creating global abstractions - unless absolutely necessary | ||
|
||
> I propose: Creating global abstractions - unless for a concrete, well known cause. | ||
|
||
3. Forcing reuse between unrelated contexts - unless absolutely needed | ||
|
||
> I propose: Forcing reuse between unrelated contexts. | ||
I wouldn't know when reuse between *unrelated* contexts would be *absolutely* needed. I guess never. | ||
As these Anti-Pattern should only be avoided and are not strictly forbidden, I don't think an exception is required. | ||
|
||
4. Obscuring flow with unnecessary indirection - unless with clear benefit | ||
|
||
> I propose: Obscuring flow with indirection - unless with clear benefit | ||
A clear benefit makes the indirection necessary and the Anti-Pattern would no longer apply. | ||
|
||
5. Separating related functionality for the sake of "clean code" - is always bad | ||
|
||
## Decision Framework | ||
|
||
When organizing code, ask: | ||
1. Does this organization make the flow more or less visible? | ||
2. Does this abstraction serve the immediate context or is its benefits so large it is warranted? | ||
|
||
> If my problem is realizing a flow, that is a valid question. If the flow is not the dominant problem, this question does not help. | ||
|
||
2. Does this abstraction serve the immediate context or are its benefits so large it is warranted? | ||
3. Is this repetition isolated or truly harmful? | ||
4. Are these boundaries emerging from the domain or being forced? | ||
5. Does this structure help or hinder understanding the complete story? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examples are requirements like