05. CS + engineering notes

Written in

by

Being an engineer shouldn’t be only about post-mortems.
Considering the next generation of programmers and my own potential to be a mentor to them, I recognize the obstacles that impede progress in adaptation. The trajectory is clear: I need to be more intuitive about the implications for software engineering in the future. The sheer amount of tools and knowledge to absorb is daunting, even with guidance and a leader. But, I now possess the wisdom to recognize that being an engineer should not be a demoralizing experience… and maybe something better can land in our laps from our tops.
////////////

### CS Reading Notes

Valuable Lessons from Books in Computer Programming

| **Element** | **What’s It About** |

  1. | Conquer complexity | High quality code manages complexity. High quality code exposes people reading it to consistent levels of abstraction separated by clear boundaries. |
  2. | Pick a process | Having a process is important. It does not matter exactly what the process is; in fact, the process should be tailored to the problem at hand. |
  3. | Prepare for collaboration and hand-off | Make code readable first, and only optimize when you can make measurable improvements to measurable performance bottlenecks. |
  4. | Contribute | Write libraries that will support the programming features you want for the problem at hand. |
  5. | Conventions have timed utility | Some conventions are better than others, but for the most part, conventions tend to be arbitrary. However, having conventions makes code easier to read and modify because a convention can communicate a lot without using much space or requiring much thinking. |
  6. | Nesting, why? | Higher level code should be supported by lower level code that hides implementation specific details from the higher level code. When done well, this makes the code easier to read and easier to modify.<br>High quality classes and routines provide consistent abstractions, document their assumptions, and check their invariants defensively |
  7. | Keep going | Requirements are rarely fixed in stone, bugs are always present, and developers can always find a better way to rewrite code. Iteration gives all of these improvements a chance to actually make it into the product under development. |
  8. | Do not fear missing out | No one convention, process, or tool set is the be all and end all of software development. Developers should be wary of absolutes and try to avoid blind faith in the processes they use. Solutions should be adapted to the problem at hand, not vice versa. |
  9. | on Variables | data initialization (do it close as close to the declaration as possible), variable scope (keep it as small as possible), limiting variables to a single purpose, effective variable names (keep them specific, use a naming conventions) |
  10. | on Statements | deep nesting of control structures tends to make code complex. If possible, it should be avoided by restructuring the code or factoring the nested code into its own routine. The more paths there are through a code fragment, the more complex it is; the number of paths a developer must consider at a single time should be minimized. |

 

“Programs must be written for people to read, and only incidentally for machines to execute.”

“Computational processes are abstract beings that inhabit computers. As they evolve, processes manipulate other abstract things called data. The evolution of a process is directed by a pattern of rules called a program. People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells.”

“Software design as taught today is terribly incomplete. It talks only about what systems should do. It doesn’t address the converse—things systems should not do. They should not crash, hang, lose data, violate privacy, lose money, destroy your company, or kill your customers.”

—-

SRP (Single Responsibility Principle): a class or module should have one, and only one, reason to change.

—-

The ideal number of arguments for a function is __zero (niladic)__. Next comes __one (monadic)__, followed closely by __two (dyadic)__. __Three arguments (triadic)__ should be avoided where possible. __More than three (polyadic)__ requires very special justification and then shouldn’t be used anyway.
A function __should not do__ more than one thing.
Stepdown rule: every function should be followed by those at the next level of abstraction (low, intermediate, advanced).


 

“Computer Programming Psychology by Gerald”__ /booknotes/

  • Programming is not an easy activity to grasp for humans, as it requires understanding limitations in the language, machine, and one’s own capabilities. The context of programming, or the environment, also makes it complex. This can lead to difficulties in understanding and modifying code. A programmer must learn how the program works for the user, while the user must understand the intentions of the program to interface correctly. A successful program must be correct, efficient, and adaptable.
  • To study and evolve programming, it is important to understand the social structure and culture of programmers. This can be approached as an anthropological study, exploring the relationship between problem solving, personality, and how programmers interact with each other and non-programmers. The tools used by programmers, such as languages and operating systems, also play a role in shaping their behavior.
  • Why is programming hard and convoluted? There are limitations on 3 things: the language, the machine, and your capability. All of those are hard to learn, and recognize. All of those are implicit elements. Programming is a behavior, not a process.. __/(* How do we facilitate a platform where we could read, and understand code, and only learn to modify?)/__
    – The most important specification your program should have: Correct. It needs to work. Any program that works is better than one that doesn’t.
    – His example of how modular the experience of programming is, tells me that we need interior designers who know frameworks, what they do, and what they serve, and guide the product owner, so he can guide his engineers by providing them a scoped out definition of the intention and how it can manifest in the world. An interior designer always gives options, and if the customer is undecided, the interior designer can make suggestions by asking some questions about their preference, or plan. If no answer is given, the interior designer makes an executive choice.
    – Multiple specifications are the reason we have complicated programming. And a program that is late is as good as worthless (or a program that does not work).
    – A program that is not adaptable will fall out of relevancy very soon. On the other hand, program that is efficient takes advantage of the specific details in this environment and problem.
    – Managers do not understand that increasing efficiency, does not mean less costly modifications. In fact, it could mean the opposite. You cannot ask for both efficiency and adaptability.
    – This points to increasing /effectiveness/ without sacrificing adaptability for future integrations.

To study programming and evolve, we need to study the social structure programmers take:

– Without introspection, any investigation would be sterile. without investigation, any introspection would lack direction. If we observe a hundred programmers, what will we find?
– The most useful model is to treat computer programming as an anthropological endeavor: a shared set of beliefs and activities which shape their day-to-day activities. How do problem solving and personality interact in the following areas:
– How do programmers relate to other programmers, and non-programmers? Are there any other distinctions in their individuality? The programmer shapes the program. The program shapes the programmer. aka “social structure”
– The programmer’s tools aka “material culture”: languages, operating systems, and other devices.
– You’re not born a programmer: knowing these things will uncover how programmers are recruited or should be recruited.

Common pitfalls to avoid when we study the behavior of programmers and their culture:

– Using introspection without validation.
– Using observations taken from a narrow base.
– Observing the wrong variables.
– Interfering with the observation.
– Generating too much data without leading to destination.
– Overly constraining experiments.
– Using unjustified precision.

 


 

The Programing Group: programming as a social activity

– Programmers do not ordinarily work in isolation. Principle of common understanding is a lost art.
– Informal mechanisms always exist: there is a relationship between physical structures and social structures. It is further complicated by the fact social learning is responsible for how programmers learn:
– In culture restructuring, there is a change in the messages exchanged within the culture, which is accomplished by persuading people to change how they think. Said another way, it is a paradigm shift.

 


 

__Working with Legacy Code__

“`
“The Legacy Code Change Algorithm

When you have to make a change in a legacy code base, here is an algorithm you can use.
1. Identify change points.
2. Find test points.
3. Break dependencies.
4. Write tests.
5. Make changes and refactor.”
“`

– How the app behaves is the main point of concern. We need to make sure the app keeps behaving, behaves better, includes new behaviors, or change behavior entirely. No matter what: If we add behavior, we end up changing behavior.
– This is largely due to “dependencies” in software development. If we can create seamless integration between all dependencies, then we would have already.
– Refactoring code is about improving the design, to alter its structure it more maintainable. It is done by making a series of small modifications, that are structural in nature. By definition, nothing about the function should change, but it’s not as insignificant as say, code clean up and formatting.
– Optimization is more open-ended in nature than refactoring, since the measured resource is either time or memory, and it requires an approach of a solution before you make structural changes.

Preserving behavior is difficult and complicated. And it’s impossible to avoid change.

| __Modification type__ | impact area 1 | impact area 2 |
| ———————— | ————————————— | ————- |
| new functionality | when adding a feature | \- |
| repair functionality | when fixing a bug | \- |
| structure and frameworks | when adding a feature, AND fixing a bug | refactoring |
| resource use | when optimizing | \- |

Unit tests run fast. If they don’t run fast, they aren’t unit tests.

How do we create unit tests, when we need them to test new code changes, when adding the tests requires a change in code?

Other kinds of tests often masquerade as unit tests. A test is not a unit test if:

1. It talks to a database.
2. It communicates across a network.
3. It touches the file system.
4. You have to do special things to your environment (such as editing configuration files) to run it.”

Mocks are one of the most valuable tools in writing unit tests.

– A seam is where the process calls the method, and the enabling point is the defined class path.
– If you put a break point, or an error point, in any piece of code, you will be able to run any app and see where it halts. Knowing where to place break points is an artform in itself, imo.


 

#### /practical approach to modifying code, or making a code change:/

Sprout Method

When you need to add a feature to a system and it can be formulated completely as new code, write the code in a new method. Call it from the places where the new functionality needs to be. You might not be able to get those call points under test easily, but at the very least, you can write tests for the new code.

1. Identify where you need to make your code change.
2. If the change can be formulated as a single sequence of statements in one place in a method, write down a call for a new method that will do the work involved and then comment it out. (I like to do this before I even write the method so that I can get a sense of what the method call will look like in context.)
3. Determine what local variables you need from the source method, and make them arguments to the call.
4. Determine whether the sprouted method will need to return values to source method. If so, change the call so that its return value is assigned to a variable.
5. Develop the sprout method using test-driven development.
6. Remove the comment in the source method to enable the call.

I recommend using Sprout Method whenever you can see the code that you are adding as a distinct piece of work or you can’t get tests around a method yet. It is far preferable to adding code inline.

Here are the steps for Sprout Class:

1. Identify where you need to make your code change.
2. If the change can be formulated as a single sequence of statements in one place in a method, think of a good name for a class that could do that work. Afterward, write code that would create an object of that class in that place, and call a method in it that will do the work that you need to do; then comment those lines out.
3. Determine what local variables you need from the source method, and make them arguments to the classes’ constructor.
4. Determine whether the sprouted class will need to return values to the source method. If so, provide a method in the class that will supply those values, and add a call in the source method to receive those values.
5. Develop the sprout class test first (see test-driven development (88)).

6. Remove the comment in the source method to enable the object creation and calls.

Here are the steps for the first version of the Wrap Method:

1. Identify a method you need to change.
2. If the change can be formulated as a single sequence of statements in one place, rename the method and then create a new method with the same name and signature as the old method. Remember to Preserve Signatures (312) as you do this.
3. Place a call to the old method in the new method
4. Develop a method for the new feature, test first (see test-driven development (88)), and call it from the new method

In the second version, when we don’t care to use the same name as the old method, the steps look like this:

1. Identify a method you need to change.
2. If the change can be formulated as a single sequence of statements in one place, develop a new method for it using test-driven development (88).
3. Create another method that calls the new method and the old method.

TDD vs DDD?

1. Test driven development is revolutionary: it starts with a failing test case. We want to create something that outputs something right, and then we write a wrong or false input. Then write the program, till we make it pass.

“For legacy code, we can extend the TDD algorithm this way:

0. Get the class you want to change under test.
1. Write a failing test case.
2. Get it compiled.
3. Make it pass. (Try not to change existing code as you do this.)
4. Remove duplication.
5. Repeat.”

In refactoring, renaming a class is the most powerful. It makes people notice things they haven’t before.

When in doubt, pass null. If it forms an exception, at least now you know.

“Here is a little algorithm for writing characterization tests:

1. Use a piece of code in a test harness.
2. Write an assertion that you know will fail.
3. Let the failure tell you what the behavior is.
4. Change the test so that it expects the behavior that the code produces.
5. Repeat.”

Every time you use a method, write a test for it. You can skip writing test for your own code for now.

“When we refactor, we generally have to check for two things: Does the behavior exist after the refactoring, and is it connected correctly?

Many characterization tests look like “sunny day” tests. They don’t test many special conditions; they just verify that particular behaviors are present. From their presence, we can infer that refactorings that we’ve done to move or extract code have preserved behavior.”

“Write tests for the area where you will make your changes. Write as many cases as you feel you need to understand the behavior of the code.”

API calls:

“If we try to separate the code’s responsibilities, we might end up with something like this:

1. We need something that can receive each incoming message and feed it into our system.
2. We need something that can just send out a mail message.
3. We need something that can make new messages for each incoming message, based on our roster of list recipients.
4. We need something that sleeps most of the time but wakes up periodically to see if there is more mail.

__The story of an application: before you dig__

“Tests can be grouped into objects called suites. We can run a suite with a test result just like a single test. All of the tests inside it run and tell the test result when they fail.

What simplifications do we have here?

1. TestSuites do more than just hold and run a set of tests. They also create instances of TestCase-derived classes via reflection.
2. There is another simplification, sort of a left over from the first one. Tests don’t actually run themselves. They pass themselves to the TestResult class, which, in turn, calls the test-execution method back on the test itself. This back and forth happens at a rather low level. Thinking about it the simple way is kind of convenient. It is a bit of a lie, but it is actually the way JUnit used to be when it was a little simpler.

Is that all?

No. Actually, Test is an interface.

Tags

Leave a Reply

Discover more from /woman//without///answers

Subscribe now to keep reading and get access to the full archive.

Continue reading