Notes for Implementation (Implementierung) — Final Exam
Course study interface
Implementation (Implementierung) — Final Exam
The implementation phase: its foundations and code-quality motivation, programming languages and typing, frameworks/libraries/Inversion of Control, the developer toolchain (IDE, version management, build tools), implementation guidelines (naming, magic, comments, null), software principles (KISS/DRY/YAGNI, Law of Demeter), component-oriented development (DI, Design by Contract, composition vs inheritance, builder, AOP), advanced Java object topics, and known problems (logging, error handling, localization, strings, concurrency, memory, team communication).
Implementation — Foundations and Code Quality
What implementation is, its goals and preconditions, the role of architecture (macro/micro), and why code quality matters.
Per IEEE 610.12: "(1) The process of translating a design into hardware components, software components, or both. (2) The result of the process in (1)." Implementation is the technical realization of a previously planned software project.
After requirements are defined in the analysis phase and the architecture is specified in detail in the design phase, the project is now technically realized — the previously theoretical plan becomes a runnable program.
Notes for Goals of implementation
Goals of implementation
Implementation turns the planned architecture into executable, validatable and maintainable software.
Realize the planned architecture: program the needed components, integrate them into the system, and document them.
Implement the customer's requirements and deliver on time.
Develop validatable and maintainable software.
Follow required guidelines: code must be understandable by teammates; obey company-specific and national/international rules (e.g. GDPR/DSGVODefinitionData-protection law/regulation. The lecture uses it as an example of external rules that implementation must obey because violations can create major legal and financial consequences. violations can be fined up to €20 million).
Notes for Preconditions before implementation begins
Preconditions before implementation begins
At least the following should be in place before implementation starts:
The project architecture must exist — the more detailed, the better.
A team for the realization must be defined and made familiar with the development process characteristics.
Programming language and tool support should already be chosen (switching IDE/tools later is costly and causes significant delays).
A rough implementation schedule should exist; delays can be expensive (penalties/PönalenDefinitionContractual penalties owed when agreed deadlines or obligations are missed; the lecture uses them as a reason not to plan implementation schedules too tightly.), so plan generously rather than too tightly.
Notes for Macro- vs. micro-architecture
Macro- vs. micro-architecture
Makroarchitektur (macro-architecture)
The project's overall architecture / coarse design (the "building plan"). Defined in the phases before implementation, by software architects. It splits the software into components with clear responsibilities so parts fit together (important when many people work in parallel).
Mikroarchitektur (micro-architecture)
The detailed designs describing the internal content of the macro-architecture's components (e.g. which classes have which tasks). Closer to the source code, designed by the programmers themselves, and has little/no influence on the macro-architecture.
During implementation the architecture keeps evolving — mainly the micro-architecture is adapted/refined as new knowledge (e.g. server performance), daily developer decisions, and changing stakeholder wishes (e.g. changing laws) are incorporated; experienced developers are needed to judge the impact of implementation decisions.
Notes for Why code quality matters — the Oracle Database example
Why code quality matters — the Oracle Database example
Scenario: A developer's report on Oracle Database 12.2: ~25 million lines of C code where you cannot change a single line without breaking thousands of tests; logic is held together by thousands of flags and mysterious macros; understanding one bug can take two weeks of studying ~20 interacting flags; the product only survives because of literally millions of tests; fixing one bug or adding one small feature can take months to years.
Interpretation: This confirms the lecture's claim that "fast", bad code causes huge long-term extra effort: changes break things elsewhere, productivity collapses over time, and pressure/chaos rise. Investing in good code up front is cheaper than the later "rescue rewrite" that ends in chaos again. Lasting solutions focus on good code.
Programming Languages and Typing
How programming languages are classified by execution and paradigm, the main paradigms, how to select a language, and static vs. dynamic typing.
A formal language with which data structures and algorithms can be described. Languages are classified by how they are executed and which paradigm they are based on.
Classification by execution: compiled to machine code (e.g. C++), interpreted (e.g. JavaScript), or compiled to bytecode for a VM (e.g. Java). Different components may be written in different languages, and some languages can be auto-converted to others (e.g. Google Web Toolkit).
Notes for Programming paradigms
Programming paradigms
Top-level paradigms and their sub-paradigms (one possible classification; some literature places OO outside imperative).
Non-structured: the first paradigm; allows Turing-completeDefinitionPowerful enough to express any computable algorithm, assuming enough time and memory. algorithms (e.g. early BASIC).
Structured (late 1950s): avoids spaghetti code from goto via subroutines, loops, block structures.
Imperative: statements the computer follows; describes HOW a goal is reached. Sub-paradigms: procedural, object-oriented.
Declarative: describes the desired result; no explicit control flow. Sub-paradigms: functional, logic-based, database query languages.
Procedural (imperative): statements via function calls; no class concept, so no inheritance (e.g. Fortran, COBOL, C, Go).
Object-oriented (imperative): based on objects with attributes (values) and methods (code); class-based (objects are instances of classes; C++, Java) or prototype-based (objects are primary entities; JavaScript, Lua).
Functional (declarative): computations as math functions of their parameters only; f(x,y,z) always returns the same result for the same input → no side effectsDefinitionChanges outside a function's returned value, such as modifying state, performing I/O, or depending on mutable external data. (Haskell, Erlang, Clojure).
Logic-based (declarative): based on formal logic; each statement expresses a fact or rule about the domain (Prolog, ALF).
Multi-paradigm languages mix several concepts so the programmer picks the best for the problem (C#; Java with imperative+OO+functional lambdas). Runtimes are especially flexible (C# + F#, Java + Scala).
Notes for Choosing a programming language and its impact
Choosing a programming language and its impact
Selection criteria: team's knowledge/experience; fit for the problem domain (enterprise vs mobile/embedded); availability of tools/frameworks/libraries; customer requirements; language adoption, documentation and help; availability of developers on the market.
Impact: language choice strongly affects the implementation phase but is rarely directly responsible for project failure; mastering the language is necessary but not solely decisive for success.
Notes for Static vs. dynamic typing
Static vs. dynamic typing
Static typing
Types are checked at compile time (Java is statically typed). Errors are caught early, types document the code and enable tooling (e.g. the generics-heavy removeMatches example relies on the compiler enforcing the wildcard types).
Dynamic typing
Types are checked at runtime. More flexible and concise for quick development, but type errors surface only while running.
Use static typing when correctness, tooling and maintainability matter (large/long-lived systems); dynamic typing suits quick, flexible scripting. Java's Java-10 local-variable type inference (var)DefinitionJava infers the local variable's static type from the initializer, so `var` reduces boilerplate without turning Java into a dynamically typed language. keeps static typing while reducing verbosity.
Frameworks, Libraries and Inversion of Control
What frameworks and libraries are, their benefits, drawbacks, selection and types, the whitebox→blackbox evolution, Inversion of Control, and interfaces vs. abstract classes.
"An application skeleton that the developer can adapt (specialize) to implement requirements optimally" / "a reusable design, often described by a set of abstract classes and the interplay of their instances." A framework provides reusable behavior: your code is called by the framework.
Benefits: software reuse, effort reduction (development is reduced to specializing given structures), and operating-system support.
Notes for Framework advantages and disadvantages
Framework advantages and disadvantages
Advantages: lower development cost and shorter development time; reuse of source code and proven architectural structures; standard compliance (e.g. JPADefinitionJava Persistence API; the lecture uses it as an example of a standard that a framework can help satisfy.); good scalability; focus on the essentials (standard problems solved automatically); robustness; security.
Disadvantages: inheriting the framework's bugs; learning effort; the project is limited to the framework's possibilities (Android projects run only on Android); the project is hard to separate from the framework; more flexible (multi-platform) frameworks make it harder to exploit platform-specific features; debugging is harder because internal framework specifics are hard to see from outside.
Notes for Choosing and adapting framework types
Choosing and adapting framework types
There is no general rule, but possible criteria and the framework-vs-own-development decision:
Criteria: an external factor (customer mandates it); the framework is an integral part of an IT architecture that new projects must connect to (also influencing the language choice); coverage of the requirements; whether maintenance/further development is secured (more likely for widespread frameworks); commercial vs free use (open/closed source, acquisition cost, license model).
Choosing the right framework is essential — a later swap is often almost a rewrite; consistent early abstraction from the framework can be 'life-saving' but greatly increases effort.
Own development (developing all parts without external frameworks) makes sense when no suitable framework exists, adapting an existing one would be at least as costly, or special non-functional requirements demand it.
Persistence frameworks: abstract DB access (session handling, caching); key feature Object-Relational Mapping (ORM)DefinitionMapping between objects in the program and relational database structures, so database access can be abstracted by the persistence framework.; e.g. Hibernate, JPA.
Web-application frameworks: separate presentation (HTML) from business logic; e.g. JBoss SEAM, Struts, Spring Webflow, Ruby on Rails, Grails.
Rich-client frameworks: build composite applications extensible via plugins/composites; e.g. Eclipse RCP.
Meta frameworks: homogenize several frameworks. (The SE1 exercise servers use Spring, which uses IoCIoC explained below
Inversion of Control (IoC)
"Don't call us, we call you": not your code but the framework decides when which code is called.
Advantage — modularization: behavior is defined in small pieces (methods, classes) whose form the framework prescribes.
Advantage — Open-Closed principle: parts can be extended, swapped, or added independently (e.g. endpoint methods in a Spring server).
Advantage — little configuration: the framework auto-detects where your code is (e.g. by class name or annotation) instead of explicit config.
Disadvantage — less abstraction: to get those advantages the framework enforces a fixed structure and conventions; missing functionality or switching frameworks then causes large effort.
Whitebox frameworks (early stage): require knowledge of the framework's internal structure; specialized via inheritance — framework given as abstract class, you implement method hooks.
Blackbox frameworks (mature): many ready components instantiated and handed to the framework, which binds them via composition; new functions via new components.
Hot spots: points where a framework is adapted to the desired behavior (components, method hooks). Method hooks: abstract methods you override to plug in behavior.
Notes for Inversion of Control (IoC)
Inversion of Control (IoC)
"Don't call us, we call you": not your code but the framework decides when which code is called.
Advantage — modularization: behavior is defined in small pieces (methods, classes) whose form the framework prescribes.
Advantage — Open-Closed principle: parts can be extended, swapped, or added independently (e.g. endpoint methods in a Spring server).
Advantage — little configuration: the framework auto-detects where your code is (e.g. by class name or annotation) instead of explicit config.
Disadvantage — less abstraction: to get those advantages the framework enforces a fixed structure and conventions; missing functionality or switching frameworks then causes large effort.
Notes for Framework vs. library
Framework vs. library
Library (Bibliothek)
Reusable functionality. Per IEEE 610.12 "a controlled collection of software and related documentation designed to aid in software development, use, or maintenance." Methods of the library are called from your own code — you decide when functionality runs (e.g. networking, XML processing).
Framework
Reusable behavior. Your code is called by the framework — the framework decides when a part of your code runs. Frameworks can in turn include libraries.
The control direction is the key difference: with a library you call it; with a framework it calls you (Inversion of Control).
Notes for Interfaces vs. abstract classes
Interfaces vs. abstract classes
Interface
A pure contract of method signatures (a type can implement many interfaces). Use to define capabilities decoupled from implementation; the Interface Segregation Principle (ISP) favors fine-grained, use-case-specific interfaces over one large universal interface.
Abstract class
May provide shared state and partial implementation plus abstract methods (single inheritance). Abstract classes let you quickly build simple frameworks: the abstract class prescribes the structure and you implement the abstract method hooks (Inversion of ControlIoC explained above
Inversion of Control (IoC)
"Don't call us, we call you": not your code but the framework decides when which code is called.
Advantage — modularization: behavior is defined in small pieces (methods, classes) whose form the framework prescribes.
Advantage — Open-Closed principle: parts can be extended, swapped, or added independently (e.g. endpoint methods in a Spring server).
Advantage — little configuration: the framework auto-detects where your code is (e.g. by class name or annotation) instead of explicit config.
Disadvantage — less abstraction: to get those advantages the framework enforces a fixed structure and conventions; missing functionality or switching frameworks then causes large effort.
Prefer interfaces for capability contracts and multiple implementations; use abstract classes to share implementation and build whitebox-style frameworkswhitebox frameworks
Choosing and adapting framework types
There is no general rule, but possible criteria and the framework-vs-own-development decision:
Criteria: an external factor (customer mandates it); the framework is an integral part of an IT architecture that new projects must connect to (also influencing the language choice); coverage of the requirements; whether maintenance/further development is secured (more likely for widespread frameworks); commercial vs free use (open/closed source, acquisition cost, license model).
Choosing the right framework is essential — a later swap is often almost a rewrite; consistent early abstraction from the framework can be 'life-saving' but greatly increases effort.
Own development (developing all parts without external frameworks) makes sense when no suitable framework exists, adapting an existing one would be at least as costly, or special non-functional requirements demand it.
Persistence frameworks: abstract DB access (session handling, caching); key feature Object-Relational Mapping (ORM); e.g. Hibernate, JPA.
Web-application frameworks: separate presentation (HTML) from business logic; e.g. JBoss SEAM, Struts, Spring Webflow, Ruby on Rails, Grails.
Rich-client frameworks: build composite applications extensible via plugins/composites; e.g. Eclipse RCP.
Meta frameworks: homogenize several frameworks. (The SE1 exercise servers use Spring, which uses IoC via reflection.)
Whitebox frameworks (early stage): require knowledge of the framework's internal structure; specialized via inheritance — framework given as abstract class, you implement method hooks.
Blackbox frameworks (mature): many ready components instantiated and handed to the framework, which binds them via composition; new functions via new components.
Hot spots: points where a framework is adapted to the desired behavior (components, method hooks). Method hooks: abstract methods you override to plug in behavior.
The developer toolchain — integrated development environments, version/source-code management (terms, branching, types, collaboration strategies), and build management with dependency management.
Notes for Integrated Development Environment (IDE)
Integrated Development Environment (IDE)
An IDE is a program that fully supports developing software — the developer's central workspace, giving access to all needed tools through one customizable interface. The IDE choice depends on the frameworks and language (e.g. Eclipse, NetBeans, IntelliJ, XCode, KDevelop, Visual Studio, Android Studio).
Team communication, refactoring, and automatic checks
Refactoring is reworking an existing implementation to improve it (readability, extensibility, maintainability) without changing behavior. Recommended approach: first build a working (even suboptimal) solution, then refactor it to a "good enough" state — like the scouts' rule of leaving code cleaner than you found it. Successful refactoring needs tests (to confirm behavior is preserved).
Refactoring example (movie/picture rental leaseOverview): violates several principles — a long method doing many things (SRP), a switch on a price-code magic constant, and mixing computation with String/HTML formatting (so a new HTML output would force duplication). Improve by extracting amount/points calculation, replacing the switch (polymorphism/enums), and separating computation from formatting.
Refactoring example (PrintPrimes): poor names (single-letter M/RR/CC, JPRIME), magic numbers, and tangled loops mixing prime computation with paged printing — split into well-named methods that compute primes and that format/print pages.
Linters / automatic code review: tools that statically analyze source for bugs, style violations and improvement potential (not every review needs a human). Pros: fast, consistent, cheap, run on every commit; Cons: limited to detectable patterns, false positives, cannot judge design intent — so they complement, not replace, human review (and newer AI-based commit analysis extends this further).
A mix of techniques: regular meetings, calls, chat; mail and a shared wiki; issue trackers (Jira, GitIssues) to discuss, assign and manage work packages, bugs and features.
Document steps, dates and decisions (meeting notes) and communicate problems early; make code and its changes traceable (e.g. include the issue number in branch names 'footer redesign [issue 53]' and commit messages; see Conventional Commits).
Example: Linux kernel development uses mailing lists for developer communication.
Go to block; build tools & dependency managementbuild tools below
Build management and dependency management
A build management system is a tool to run the recurring tasks of building an application automatically. A naive approach — shell/batch scripts — is platform-dependent, poorly portable, and grows into large unmanageable scripts; better to use dedicated tools: make (C/C++ on Unix), Maven and Ant (Java), rake (Ruby), nAnt (.NET).
Why use one: many tasks recur (compiling, pre/post-processing, generating docs, running tests, dependency management); manual repetition is error-prone, time-consuming, and hurts reproducibility (obvious steps go undocumented).
Dependency management: keep library versions consistent; avoid incompatibilities with older versions; minimize and auto-resolve side effects (one library needing another).
Open dependency questions: how to detect that newer libraries contain bugs? is a newer version still compatible? if auto-updating to the newest libraries, how to ensure no security problems? how to handle changed default behavior?
Go to block; unit testing; library import; export to stores; UI editor; full-text and semantic search (AST-based)ExplanationSearch that uses knowledge about the programming language structure, represented by the Abstract Syntax Tree, instead of matching only plain text.; item tracking; repository sync (SCM)SCM defined below
"Version management systems are computer systems that archive changes to files and directories over time and make them available in one or more branches for collaborative editing by several people."
Reasons to use it: teamwork (code accessible to all, parallel editing, traceability of who changed what when — emailing files just causes chaos); managing not only source but also binaries, images, text documents; restoring older versions of a file.
Go to block; configuration management; plugin extensibility.
Advantages: everything in one place without media breaks; manage several projects; auto-generate boilerplate; autocompletion; included language docs; debugging tools; tailored UI.
Disadvantages: learning curve / complexity; rarely-used tools well hidden; not ideal for first learning a language.
Notes for Versionsmanagement bzw. Source Code Management
"Version management systems are computer systems that archive changes to files and directories over time and make them available in one or more branches for collaborative editing by several people."
Reasons to use it: teamwork (code accessible to all, parallel editing, traceability of who changed what when — emailing files just causes chaos); managing not only source but also binaries, images, text documents; restoring older versions of a file.
Notes for Version-management concepts and SCM practice
Version-management concepts and SCM practice
Repository: the data store from which the working copy is generated.
Checkout: fetching data from the repository; Check-in/Commit: transferring new versions into the repository (with a short commit message; atomic — all or nothing).
Tagging: marking a concrete development state (e.g. a finished version).
Logging/history: records which changes were made by whom and when, allowing restoration of any past state.
Branching: a copy of objects (e.g. a stable main version vs an experimental test version, or one branch per customer). A stable version stays untouched by tests/broken intermediate states. Drawback: a long-lived branch can cause many merge conflicts — Trunk-Based Development is an alternative.
Benefits overall: collaborative work; insight into version history; automatic documentation of changes (what/why changed, which requirements); easy integration into automated processes (CI & deploymentCI/CD compared later
Continuous Integration, Delivery, and Deployment in practice
Continuous Integration (CI)
Commit changes to the master branch as often as possible; each change is validated automatically by building the application and running automatic tests (build + test automated).
Continuous Delivery
Extends CI with an automated release process: build, test and the deploy-to-staging + acceptance tests are automated, but the deploy to production stays a manual (button) step.
Continuous Deployment
Goes one step further than Continuous Delivery: if all stages pass successfully, every update goes to the customer automatically (deploy to production and smoke tests automated too).
Self-managed deployment with Gradle
Gradle suffices for most simple continuous-deployment needs: gradle build (resolve dependencies + build), gradle test (unit tests, plus smoke/acceptance tests via e.g. Gatling), and SSH plugins to transfer built jars / restart servers on a new release.
GitLab CI/CD
GitLab integrates CI & CD: a .gitlab-ci.yml script runs on every commit (compile, test, linters) with stages like build and test; the faculty GitLab has a shared runner so student group repos can use CI/CD directly, and success/failure is documented in GitLab.
Software passes development stages of rising quality (local development → test system → QA system → production). The pipeline is build → test → deploy to staging → acceptance tests → deploy to production → smoke tests; CI/Delivery/Deployment differ only in how far down that pipeline automation reaches.
Why distributed SCM prevailed: offline-capable local repositories, fast local operations (commits/branches anytime), resilience (no single point of failure), and easy branching/merging encouraging collaboration.
Before such systems: files were shared via email/FTP/shared folders → chaos, no traceability, easy overwrites, painful integration.
Branching was unpopular in older systems because merges were hard/expensive, so teams avoided branches and integrated less often.
Branch drawbacks: long-lived branches drift and cause large merge conflicts (mitigated by Trunk-Based Development).
Tagging links to releases: a tag marks the exact state shipped as a version.
Commit & Test & Revert: any commit whose changes fail the automated (unit) tests is automatically discarded — an extreme style that can lose code, proposed to force always-green, well-tested small commits.
Notes for Version-management types
Version-management types
Local
Versions individual files only; oldest form; not suitable for team projects (e.g. RCS, SCCS).
Central
Client/server: a central server holds the repository, connected to many clients (e.g. Subversion/SVN, CVS, Perforce).
Decentral / distributed
No central repository — every developer has their own; changes are exchanged between repositories, though one is usually declared the "main" repository, cloned to a local copy. Commits merge into another repository and conflicts from parallel edits must be resolved. Advantage over central: almost all operations (commits, branches) work anytime, even offline (e.g. Git — now the industry standard, Mercurial, Bazaar).
Notes for Collaboration strategies — Lock-Modify-Unlock vs. Copy-Modify-Merge
Collaboration strategies — Lock-Modify-Unlock vs. Copy-Modify-Merge
Lock-Modify-Unlock (Pessimistic Revision Control)
Lock a file so only one developer can edit it: check out before editing, check in (unlock) when done. Prevents conflicts but serializes work on a file.
Copy-Modify-Merge (Optimistic Revision Control)
Allow conflicts: everyone can edit any file anytime, but committing a file changed in the meantime raises merge conflicts that must be resolved (automatically by the tool or manually) before check-in.
Without a strategy, simultaneous changes would simply overwrite each other (only the last push survives). Refactoring (renaming/moving files) is best done via the SCM's move command rather than delete+add; beware IDE refactoring when the SCM is not plugged in.
Notes for Build management and dependency management
Build management and dependency management
A build management system is a tool to run the recurring tasks of building an application automatically. A naive approach — shell/batch scripts — is platform-dependent, poorly portable, and grows into large unmanageable scripts; better to use dedicated tools: make (C/C++ on Unix), Maven and Ant (Java), rake (Ruby), nAnt (.NET).
Why use one: many tasks recur (compiling, pre/post-processing, generating docs, running tests, dependency management); manual repetition is error-prone, time-consuming, and hurts reproducibility (obvious steps go undocumented).
Dependency management: keep library versions consistent; avoid incompatibilities with older versions; minimize and auto-resolve side effects (one library needing another).
Open dependency questions: how to detect that newer libraries contain bugs? is a newer version still compatible? if auto-updating to the newest libraries, how to ensure no security problems? how to handle changed default behavior?
Implementation Guidelines
Code conventions and why they matter, magic numbers and least surprise, naming conventions, source-code comments, and the handling of null.
Notes for Code conventions, standardization, and readability
Code conventions, standardization, and readability
"Coding conventions are rules for a programming language that suggest programming style, procedures and methods." They cover indentation, commenting, naming, and environment aspects (data structure, packaging). For each project conventions should be set and kept so the code stays consistent regardless of who wrote it; most IDEs can define and auto-check them.
Readability: conventions keep code readable; in real projects ~99% of 'implementation time' is reading/understanding existing code (read 99 lines, change one).
Maintainability: >80% of a software's lifetime is maintenance, rarely by the original developer — others must onboard easily.
Customer satisfaction: when source code is the delivered product, it must be cleaned and carefully packaged like any product.
Language quasi-standard: conventions partly act as a de-facto standard for the language.
Reporting: an overview of key software-process metrics (unit-test results, ToDos, upcoming deadlines, open bugs).
Standardization: keeping certain standards to obey legal rules (e.g. medical domain), ease maintenance, and enable efficient further development.
Three groups of standards: national/international (ISO, DIN, IEEE), company-wide, project-specific; also standards on protocols, platforms, tool conventions, best practices. Often entails considerable documentation effort.
Readability example scenario: A C one-liner intended to concatenate strings: "void strcat(char *s, char *t){ WHILE(*t++); FOR(t--;*t++=*s++;); }". Its purpose is almost impossible to read at a glance.
Readability example interpretation: Unreadable code forces every reader to reverse-engineer intent, multiplying maintenance cost. Readability and understandability are first-class goals: speaking names, simple structure and clear intent let other developers (and future you) understand code quickly and correctly.
Notes for Magic numbers/strings, least surprise, and Command-Query Separation
Magic numbers/strings, least surprise, and Command-Query Separation
Magic numbers reduce readability: if(map.size() < 2) raises questions — what is 2? can I change it? where else? Fix with explicit final variables (minMapAmount) or methods (minMapAmountReceived()).
Magic also applies without numbers (e.g. magic strings); the fix is the same: name the value/concept (and connect to enums for fixed sets of constants).
Surprising method behavior: a method name must clearly and completely reflect its behavior; otherwise one would have to read every method's code before each call (which people skip → avoidable debugging).
If a method name gets too long it unites too much behavior — split it.
Command-Query Separation: methods are either a query (returns data, no side effects — unproblematic) or a command (changes state — handle with care); separating them avoids surprises.
Notes for Naming conventions and recommendations
Naming conventions and recommendations
Use speaking names (content & purpose), no abbreviations, prefer ASCII; classes as nouns, methods/functions as verbs; CamelCase; constants (final static) in ALL_CAPS; enable IDE auto-formatting.
Variable names: short, based on stored values; the longer the scope, the longer the name (loop var i/j for short bodies, rowIndex for longer ones; mark loop values, e.g. for(Photo eachPhoto : photoCollection)).
Return variable named result, defined at method start; conditions: do not drop braces (saved time is lost to debugging).
Formatting exkurs: 2–4 spaces indentation; 120–160 char line length (wide screens); break after commas/operators to avoid horizontal scrolling.
Type inference (var, Java 10+): only for local variables; names become more important, restrict scope, never let readability depend only on the IDE, compensate the information types provide. Use for long generic types (Optional<Map.Entry<String,Long>>) or when the creation gives enough info; pitfalls: var list = new ArrayList<>() becomes ArrayList<Object>, var val = 0 becomes int.
Notes for Source-code comments
Source-code comments
Comments let a programmer embed readable notes in source code (ignored by compilers/interpreters). Write them not for yourself but for the teammate extending your code years later, and to reduce queries and bug reports from misuse. Comments should only add extra info — the code itself should be shaped so it is easier to use correctly than incorrectly even without comments.
Two kinds: block comments (/* ... */, may nest depending on language) and line comments (// to end of line).
Use for: background of a function; decisions/assumptions made (why this algorithm/pattern); temporary debugging ('commenting out' — delete long-commented code, restore via SCMSCM defined above
"Version management systems are computer systems that archive changes to files and directories over time and make them available in one or more branches for collaborative editing by several people."
Reasons to use it: teamwork (code accessible to all, parallel editing, traceability of who changed what when — emailing files just causes chaos); managing not only source but also binaries, images, text documents; restoring older versions of a file.
Go to block); tags (// TODO, // FIXME, // NOTE) findable via search/IDE.
Always comment: surprising behavior (when/why exceptions are thrown), expected inputs and returned results, dependencies between methods/classes (A must be called before B).
Use a standard style like Javadoc (@param, @return, simple HTML tags; first sentence = summary); doc generators (Javadoc, Doxygen) build documentation from specially marked comments.
Good vs bad: a comment merely repeating the method head adds no value (Bad: 'Sets the tool tip'); a good comment defines the concept, context and behavior (when the tooltip shows, what null means).
Comment claims (worksheet): comments are not a substitute for bad code; if code expressed intent well enough, fewer comments would be needed — but comments still have a legitimate job (explaining WHY/intent and contracts), so they are not merely an admission of failure.
Notes for Handling null — never give null a meaning
Handling null — never give null a meaning
Avoid null; as soon as null appears in code, NullPointerExceptions (always an avoidable programming error) follow sooner or later. Never give null a meaning: not as a parameter value for "missing" data (use overloading/varargsDefinitionA variable-argument parameter lets callers pass zero or more values instead of using null to mean "no value supplied".addition, not part of the lecture), not as a return "signal" for errors (use exceptionsexceptions later
Exception handling and the Notification Pattern
Exception handling uses try (statements that may fail), catch (the actual handling, exploiting exception hierarchies via inheritance), and an optional finally (always runs — classic use: releasing resources like DB connections; see Closeable / try-with-resources). To find a runtime error, clarify: what happened, what should have happened, is it reproducible/regular, how long has it occurred, how important is the fix — using error logs, affected-document descriptions, and commented source.
Throwing exceptions has drawbacks for data validation: exceptions then actively affect control flow (how data is discarded); the first thrown error stops further checking, making it hard to report several errors at once; many throws on few lines hurt readability.
Checked exceptions (extends Exception): for errors that cannot be prevented even with correct programming (e.g. wrong username/password rejected by a server) and to force the developer to handle them (try/catch enforced).
Unchecked exceptions (extends RuntimeException): for errors avoidable by correct programming (e.g. accessing a non-existent list index) — try/catch possible but not mandatory. try/catch works the same for both.
Best practices: define custom exception types/hierarchies only when existing ones don't suffice (enables distinct handling; avoid catch(Exception e)); always log exceptions (level ERROR, pass the exception object); write meaningful error messages; do not use exceptions for control flow (only exceptional cases); errors must be thrown, caught and handled — all three explicit and as early as possible (avoid 'public static void main(...) throws Exception').
Hierarchies in Java: do not derive from Error/its subclasses (JVM errors like OutOfMemoryError, StackOverflowError); define your own hierarchies from Exception or RuntimeException (e.g. PlayerException, MapException) for clarity, individual handling and reuse.
In real code exceptions are pervasive: in the JDK 11/Eclipse analysis, java.util.List has 83% of methods throwing, ArrayList 48%, Iterator 75% (66% overall).
The Notification Pattern collects errors in a Notification object (addError, hasError, errorInformation) so validation can continue and report all errors together.
try/catch is only needed when combining the pattern with foreign code (as in the example); otherwise create exceptions (new) without throwing them — keep it KISS.
Always combine the Notification Pattern with exceptions: build your own exception hierarchies (Single Responsibility, inheritance); exceptions compactly carry key info — a stack trace (where), the rough error kind (concrete type) via sensible hierarchies, and at least abstract (foreign code) or detailed (own code) error details (the message).
Go to block), not as a return for missing data (use default values or Optional), not as a default for uninitialized fields.
These best practices shorten and ease reading code: unnecessary null checks and digging through docs/code for possible nulls are avoided. The Java-8 Optional addresses missing return values but should be avoided where it bloats code — prefer default values with helper methods for your own types, and return empty collections (e.g. Collections.emptyList()) for data collections. (This is the practical fix for the worksheet's "Billion Dollar Mistake"ExplanationA common nickname for null references: the worksheet uses the term for the costly class of errors caused by treating null as a normal value. / employee-list and per-null-check examples: chained null checks do not fully prevent NPEs — e.g. registry.getItem(...) may still return null.)
Software Principles and Best Practices
The KISS/DRY/YAGNI mnemonics, the Golden Hammer anti-pattern, and the Law of Demeter.
Notes for KISS, DRY, and YAGNI
KISS, DRY, and YAGNI
KISS (Keep It Small and Simple): 'as simple as possible'; at every decision ask how it could be simpler; simplicity eases understanding and avoids problems from unnecessary complexity. Related: appropriateness — add complexity only as far as actually helpful (e.g. add multi-threading only if otherwise too slow).
DRY (Don't Repeat Yourself / 'once and only once'): avoid redundancy such as duplicated code — extract a private method, vary behavior via parameters; then changes need only one place.
YAGNI (You Aren't Gonna Need It): implement functionality only once it is clearly needed — simpler, faster development and less maintenance; avoids unnecessary abstraction layers. E.g. introduce a Java interface only when at least two types implement it now, not speculatively for the future.
Notes for Golden Hammer (anti-pattern)
Golden Hammer (anti-pattern)
"If all you have is a hammer, everything looks like a nail" — the tendency to apply one familiar tool, technology, pattern or language to every problem regardless of fit.
In software development this means over-using a favorite solution/technology where it does not suit the problem, producing poor, overcomplicated or ill-fitting designs. The remedy is to know several tools/approaches and choose per problem (compare YAGNI/KISSKISS/YAGNI above
KISS, DRY, and YAGNI
KISS (Keep It Small and Simple): 'as simple as possible'; at every decision ask how it could be simpler; simplicity eases understanding and avoids problems from unnecessary complexity. Related: appropriateness — add complexity only as far as actually helpful (e.g. add multi-threading only if otherwise too slow).
DRY (Don't Repeat Yourself / 'once and only once'): avoid redundancy such as duplicated code — extract a private method, vary behavior via parameters; then changes need only one place.
YAGNI (You Aren't Gonna Need It): implement functionality only once it is clearly needed — simpler, faster development and less maintenance; avoids unnecessary abstraction layers. E.g. introduce a Java interface only when at least two types implement it now, not speculatively for the future.
Go to block and selecting the right language/framework/process model).
Notes for Law of Demeter (LoD)
Law of Demeter (LoD)
A method m of class K should only call methods of: (1) K itself, (2) objects passed as parameters to m, (3) instance variables of K, (4) variables created within m, and (5) static fields. In short: "only talk to your immediate friends", do not reach through chains of objects.
Following LoD reduces couplingcoupling example below
Composition vs. inheritance
Inheritance (is-a)
A subclass extends a superclass and reuses/overrides its behavior. Risk: it breaks encapsulation and couples to superclass internals — changes in the superclass can break the subclass.
Composition (has-a)
A class holds another object and delegates to it through its public interface, independent of the other class's internals.
"Favor composition over inheritance": composition avoids the fragile coupling to superclass internals. Worksheet example: InstrumentedHashSet extends HashSet and overrides add/addAll to count additions, but breaks because HashSet.addAll internally calls add (double counting) — fix by composing a HashSet field and delegating, counting in the wrapper.
Go to block and hidden dependencies, so changes in distant classes do not ripple in. It relates to encapsulation and information hidinginternals example below
Composition vs. inheritance
Inheritance (is-a)
A subclass extends a superclass and reuses/overrides its behavior. Risk: it breaks encapsulation and couples to superclass internals — changes in the superclass can break the subclass.
Composition (has-a)
A class holds another object and delegates to it through its public interface, independent of the other class's internals.
"Favor composition over inheritance": composition avoids the fragile coupling to superclass internals. Worksheet example: InstrumentedHashSet extends HashSet and overrides add/addAll to count additions, but breaks because HashSet.addAll internally calls add (double counting) — fix by composing a HashSet field and delegating, counting in the wrapper.
Go to block (Block 3 design principles). Violation example: City.getStreetName(Employee) calls employee.getAddress().getStreetName() — it reaches into Address (a non-friend); better to ask the Employee directly (e.g. employee.getStreetName()).
Component-Oriented Development
Goals and parts of component-oriented development, Dependency Injection, Design by Contract, composition vs. inheritance, complex object creation (builder), and aspect-oriented programming.
Notes for Component-oriented and aspect-oriented development
Component-oriented and aspect-oriented development
Component-oriented development splits functionality into individual services. "Separation of Concerns": each component has exactly one task and interacts through defined interfaces; the whole system is many loosely coupled components.
Each method/class should focus on one concern, but one often unknowingly violates this with cross-cutting concerns. In the worksheet's InventoryService.create(), logging and timing code is mixed into the business method — a cross-cutting concern duplicated across many methods.
Building blocks: Microservices — a small service serving exactly one purpose, many coupled into a whole system; Interfaces — specify exactly which data a microservice expects as input and returns as output.
Advantages: reusability (components reused in other services); scalability (scale only the component that needs more resources); components can be written in different languages and interact only via interfaces; components are easier to swap when functions change; functionality is easier to extend.
AOP extracts cross-cutting concerns (logging, timing, transactions, security/authorization, caching) into separate 'aspects' woven into the code, keeping business methods focused on one concern.
Benefit: removes duplicated cross-cutting code and keeps methods single-concern; cost/risk: added indirection/magic — behavior is no longer visible at the call site, which can make code harder to follow and debug.
Notes for Dependency Injection (DI)
Dependency Injection (DI)
DI resolves dependencies on concrete implementations, e.g. by working with interfaces — "injecting dependencies". Classes/components are decoupled from what they depend on: dependencies are not defined inside the class but passed in via constructor or setter, so the calling component supplies the dependencies at runtime. A dependency is an object a system needs to function (e.g. a service that reads data from a database).
Advantages: decouples constructing a class from constructing its dependencies; classes become as independent as possible (Inversion of Control — configure dependencies from outside); increases reusability; classes can be tested independently.
Refactor pattern (worksheet): replace internal 'new' (Service service = new Service(...) / Connection conn = new Connection()) with a constructor parameter (Customer(Service service) / Server(Connection conn)) so different services/connections can be injected and mockedmock explained later
Test drivers and test doubles
Test driver (Testtreiber)
A special test interface/layer that calls the component under test and monitors/controls its execution (drives it with test data).
Dummy
Provides only an interface with no implementation (e.g. ignores method calls); used when the caller expects no return value.
Fake
Has an implementation but only simulates functionality (often strongly abstracted), e.g. storing data in a HashMap/ArrayList instead of a real database → faster tests.
Stub
Like a fake but even more simplified — e.g. ignores modifying calls and always returns the same test data on queries.
Mock
Usually builds on a stub/fake and records details of each call, so a unit test can verify which methods were called, in what order/number, and with which parameters.
Test doubles replace/simulate parts of the software to reduce external dependencies. They rely on the Block 3/4 'good implementation' basics: Dependency Injection swaps real implementations and doubles as needed, and interfaces make it easy to create objects that simulate others.
Notes for Design by Contract (pre/post-conditions, invariants)
Design by Contract (pre/post-conditions, invariants)
Design by Contract (Bertrand Meyer; integrated in .NET/Eiffel, implementable in Java/C++ via asserts or frameworks) determines the correctness of runtime behavior and component interfaces via invariants, preconditions and postconditions. It enables runtime analysis (does code behave as expected on each run?), static compile-time analysis (e.g. a variable that should never be null), early automatic error detection, and easier component integration.
Preconditions: define the required state at/before a method call (e.g. valid input parameters): assert(input != null).
Postconditions: define the state right before/after method end (e.g. valid return values, expected object state): assert(result > 27).
Invariants: define stable object state while no code runs — between method calls and right after construction (e.g. 0 <= top <= list.size()).
Conditions can be weak (ε < 0.001) or strong (ε < 0.00001), with weak vs strong consequences (log an error vs throw IllegalArgumentException); they implicitly document how an interface may be called and what the caller can expect.
Practice: analyzing the 100 largest GitHub projects (>40M lines), ~5% of methods contain asserts; methods with asserts have fewer bugfix commits; more experienced developers add asserts more; asserts cluster at frequently-executed important points; project type does not affect assert count. Enable Java asserts via the -ea VM argument.
Notes for Composition vs. inheritance
Composition vs. inheritance
Inheritance (is-a)
A subclass extends a superclass and reuses/overrides its behavior. Risk: it breaks encapsulation and couples to superclass internals — changes in the superclass can break the subclass.
Composition (has-a)
A class holds another object and delegates to it through its public interface, independent of the other class's internals.
"Favor composition over inheritance": composition avoids the fragile coupling to superclass internals. Worksheet example: InstrumentedHashSet extends HashSet and overrides add/addAll to count additions, but breaks because HashSet.addAll internally calls add (double counting) — fix by composing a HashSet field and delegating, counting in the wrapper.
Notes for Creating complex objects — telescoping constructors vs. builder
Creating complex objects — telescoping constructors vs. builder
One way to create complex objects is a series of constructors for different combinations of optional and mandatory parameters (telescoping constructors), as in the NutritionFacts example.
Drawbacks of telescoping constructors: many overloads are hard to read and use; callers must match parameter order; easy to pass the wrong value to the wrong (same-typed) parameter; poor scalability as optional fields grow.
Alternative — Fluent Interface / Builder pattern: a builder sets parameters by name via chained calls (e.g. new NutritionFacts.Builder(servingSize, servings).calories(100).sodium(35).build()), improving readability and avoiding argument-order mistakes while keeping the object immutable.
Advanced Java Object Topics
Advanced uses of enums, and the Object methods toString(), Comparable, and clone().
Notes for Enums, Comparable, and clone()
Enums, Comparable, and clone()
Enums replace magic numbers/stringsmagic values
Magic numbers/strings, least surprise, and Command-Query Separation
Magic numbers reduce readability: if(map.size() < 2) raises questions — what is 2? can I change it? where else? Fix with explicit final variables (minMapAmount) or methods (minMapAmountReceived()).
Magic also applies without numbers (e.g. magic strings); the fix is the same: name the value/concept (and connect to enums for fixed sets of constants).
Surprising method behavior: a method name must clearly and completely reflect its behavior; otherwise one would have to read every method's code before each call (which people skip → avoidable debugging).
If a method name gets too long it unites too much behavior — split it.
Command-Query Separation: methods are either a query (returns data, no side effects — unproblematic) or a command (changes state — handle with care); separating them avoids surprises.
Go to block for fixed sets of constants (type-safe, named, exhaustively checkable) — the solution used before enums was named int/String constants, which are not type-safe.
An enum can implement a Singleton (a single-constant enum is the simplest, serialization-safe singleton in Java).
Enums can carry per-constant fields and behavior: the worksheet Operation enum stores a symbol and defines an abstract apply(double,double) overridden per constant (PLUS, MINUS) and implements a Display interface (toDisplayString) — i.e. each constant has its own method implementation.
Comparable: implementing compareTo(o) (returns 0 = equal order, -1 = this lower, 1 = this higher) defines a natural ordering; it is recommended because it integrates with the Java Collections Framework (sorting, sorted sets/maps). Pitfall: the ordering should be consistent with equalsExplanationcompareTo should return 0 for the same object pairs that equals treats as equal; otherwise sorted collections can treat distinct-looking elements as duplicates or fail to find expected entries.addition, not part of the lecture.
clone(): 'creates and returns a copy of this object; the precise meaning of copy may depend on the class' — i.e. Oracle leaves it deliberately vague, and clone() is tricky to implement correctly.
Shallow copy: copies the object but shares references to nested objects (changing a nested object affects both copies); deep copy: also copies the nested objects (fully independent).
clone() and Cloneable: clone() only works as a field-by-field copy if the class implements the marker interfaceExplanationA marker interface declares no behavior itself; a class implements it to signal a property that Java library code checks.addition, not part of the lecture java.lang.Cloneable; otherwise Object.clone() throws CloneNotSupportedException.
Notes for toString()
toString()
toString() (from java.lang.Object) returns a concise but informative, human-readable string representation of an object. It is intended for debugging/logging — not for presenting information to end users in a UI.
Do not generate CLI/UI output via toString(): it poorly supports localization. Risk: code (logs, other developers, tests) may come to rely on a particular toString() format, so later changing the implementation can silently break those consumers.
Known Problems and Best Practices
Logging, error handling, localization, string handling, concurrency, memory management, and team communication.
Notes for Logging (runtime logging)
Logging (runtime logging)
Logging records system states at runtime — vital for diagnosing errors (what led to a fault, what consequences, what was the system state). Simple System.out/System.err is not flexible enough (usually console-only; hard to redirect to files/log servers/monitoring; no distinct levels). Solution: logging frameworks (log4j, Logback, SLF4J) with log levels.
DEBUG: execution info (what was called when/how, parameter values).
INFO: essential program events (e.g. 'loading complete').
WARNING: something unexpected happened but was compensated by a fallback/default.
ERROR: an error occurred — always pass the full exception object to the logger to capture stack traceDefinitionA stack trace is the recorded chain of method calls active when the exception occurred; it helps locate where the failure originated.addition, not part of the lecture and message.
Best practice: exploit log levels to filter the millions of log entries real projects produce.
Notes for Error handling — the four fail strategies
Error handling — the four fail strategies
Fail ignore
The program carries on 'unimpressed' despite the error (e.g. a game's auto-save fails because the disk is full).
Fail recover
The problem is solved (e.g. the program re-establishes the server connection after an error).
Fail soft
The faulty part/function is switched off or falls back to default behavior.
Fail hard
The program or essential flow is deliberately terminated — used especially in safety-critical areas (e.g. TLSDefinitionTLS means Transport Layer Security, the protocol family used to protect network connections with encryption and endpoint authentication.addition, not part of the lecture connections terminate immediately on error).
Choose the strategy by scenario and consequence; an inconsistent mix of approaches confuses users. An error must not crash the application, and how to handle which errors should ideally be decided already in the design phase.
Notes for Exception handling and the Notification Pattern
Exception handling and the Notification Pattern
Exception handling uses try (statements that may fail), catch (the actual handling, exploiting exception hierarchies via inheritance), and an optional finally (always runs — classic use: releasing resources like DB connections; see Closeable / try-with-resourcesExplanationA Java try-with-resources block automatically closes resources that implement AutoCloseable/Closeable when the block exits, including on exceptions.addition, not part of the lecture). To find a runtime error, clarify: what happened, what should have happened, is it reproducible/regular, how long has it occurred, how important is the fix — using error logs, affected-document descriptions, and commented source.
Throwing exceptions has drawbacks for data validation: exceptions then actively affect control flow (how data is discarded); the first thrown error stops further checking, making it hard to report several errors at once; many throws on few lines hurt readability.
Checked exceptions (extends Exception): for errors that cannot be prevented even with correct programming (e.g. wrong username/password rejected by a server) and to force the developer to handle them (try/catch enforced).
Unchecked exceptions (extends RuntimeException): for errors avoidable by correct programming (e.g. accessing a non-existent list index) — try/catch possible but not mandatory. try/catch works the same for both.
Best practices: define custom exception types/hierarchies only when existing ones don't suffice (enables distinct handling; avoid catch(Exception e)); always log exceptions (level ERROR, pass the exception object); write meaningful error messages; do not use exceptions for control flow (only exceptional cases); errors must be thrown, caught and handled — all three explicit and as early as possible (avoid 'public static void main(...) throws Exception').
Hierarchies in Java: do not derive from Error/its subclasses (JVM errors like OutOfMemoryError, StackOverflowError); define your own hierarchies from Exception or RuntimeException (e.g. PlayerException, MapException) for clarity, individual handling and reuse.
In real code exceptions are pervasive: in the JDK 11/Eclipse analysis, java.util.List has 83% of methods throwing, ArrayList 48%, Iterator 75% (66% overall).
The Notification Pattern collects errors in a Notification object (addError, hasError, errorInformation) so validation can continue and report all errors together.
try/catch is only needed when combining the pattern with foreign code (as in the example); otherwise create exceptions (new) without throwing them — keep it KISSKISS explained above
KISS, DRY, and YAGNI
KISS (Keep It Small and Simple): 'as simple as possible'; at every decision ask how it could be simpler; simplicity eases understanding and avoids problems from unnecessary complexity. Related: appropriateness — add complexity only as far as actually helpful (e.g. add multi-threading only if otherwise too slow).
DRY (Don't Repeat Yourself / 'once and only once'): avoid redundancy such as duplicated code — extract a private method, vary behavior via parameters; then changes need only one place.
YAGNI (You Aren't Gonna Need It): implement functionality only once it is clearly needed — simpler, faster development and less maintenance; avoids unnecessary abstraction layers. E.g. introduce a Java interface only when at least two types implement it now, not speculatively for the future.
Always combine the Notification Pattern with exceptions: build your own exception hierarchies (Single ResponsibilitySRP example below
Team communication, refactoring, and automatic checks
Refactoring is reworking an existing implementation to improve it (readability, extensibility, maintainability) without changing behavior. Recommended approach: first build a working (even suboptimal) solution, then refactor it to a "good enough" state — like the scouts' rule of leaving code cleaner than you found it. Successful refactoring needs tests (to confirm behavior is preserved).
Refactoring example (movie/picture rental leaseOverview): violates several principles — a long method doing many things (SRP), a switch on a price-code magic constant, and mixing computation with String/HTML formatting (so a new HTML output would force duplication). Improve by extracting amount/points calculation, replacing the switch (polymorphism/enums), and separating computation from formatting.
Refactoring example (PrintPrimes): poor names (single-letter M/RR/CC, JPRIME), magic numbers, and tangled loops mixing prime computation with paged printing — split into well-named methods that compute primes and that format/print pages.
Linters / automatic code review: tools that statically analyze source for bugs, style violations and improvement potential (not every review needs a human). Pros: fast, consistent, cheap, run on every commit; Cons: limited to detectable patterns, false positives, cannot judge design intent — so they complement, not replace, human review (and newer AI-based commit analysis extends this further).
A mix of techniques: regular meetings, calls, chat; mail and a shared wiki; issue trackers (Jira, GitIssues) to discuss, assign and manage work packages, bugs and features.
Document steps, dates and decisions (meeting notes) and communicate problems early; make code and its changes traceable (e.g. include the issue number in branch names 'footer redesign [issue 53]' and commit messages; see Conventional Commits).
Example: Linux kernel development uses mailing lists for developer communication.
Go to block, inheritance); exceptions compactly carry key info — a stack trace (where), the rough error kind (concrete type) via sensible hierarchies, and at least abstract (foreign code) or detailed (own code) error details (the message).
Notes for Localization
Localization
Localization adapts software to its area of use (language, date and number formats; e.g. Day.Month.Year in Europe vs Month.Day.Year in America). Texts must be separated from source code — otherwise you'd need a separate source file per language.
Avoid hard-coding UI texts; extract them into resource files and reference them; name text IDs by use area (loginBtn, loginWelcomeTxtBox) not log1/log2.
Specify date/number formats as format strings; use placeholders instead of stitching strings together (Good: 'Hi %1$s, messages %2$d').
Android example: one strings.xml per language (/res/values-de, /res/values-en); the app auto-selects the matching version by device language.
Notes for String handling
String handling
Manipulation: don't combine many strings with + — strings are immutable, each + creates a new string and discards another (example: 100000 concatenations took 11174 ms with + vs 7 ms with StringBuilder). Best practice: more than ~5 +? Use StringBuilder.
Structures: represent complex indented content with text blocks ("""...""") instead of \n and + concatenation.
Formatting: use helpers (DateTimeFormatter, NumberFormat) for localized output instead of building strings with .format manually.
Notes for Concurrency and memory management
Concurrency and memory management
Parallel threads can cause problems. Race conditions: the result depends on which thread finishes first, causing unexpected behavior that is extremely tedious to debug in complex projects. Always keep this in mind with threads/async processes (network communication!): has all data arrived at the client? could an object not yet be created because the client's answer wasn't awaited?
Languages like Java/C# ease memory management by freeing memory automatically (garbage collection) when possible.
Java support: Stream.parallel for low-effort parallel processing; concurrency data structures (auto-synchronizing); synchronized & locks to coordinate flows; Atomic and ThreadLocalExplanationAtomic types provide indivisible thread-safe operations; ThreadLocal gives each thread its own separate value.addition, not part of the lecture for simple thread-safe use (e.g. ThreadLocalRandom vs the non-thread-safe Random).
Drawbacks: less control over when memory is freed; performance/pause overhead; and automatic management can still fail to reclaim memory that is no longer needed but still referenced.
Memory leak: occurs when no-longer-needed memory is not freed because it is still reachable. Worksheet Stack example: pop() does return elements[--size] but leaves the popped slot still referencing the old object (a stale/obsolete reference), so it is never garbage-collected — fix by setting elements[size] = null after popping.
Notes for Team communication, refactoring, and automatic checks
Team communication, refactoring, and automatic checks
Refactoring is reworking an existing implementation to improve it (readability, extensibility, maintainability) without changing behavior. Recommended approach: first build a working (even suboptimal) solution, then refactor it to a "good enough" state — like the scouts' rule of leaving code cleaner than you found it. Successful refactoring needs tests (to confirm behavior is preserved).
Refactoring example (movie/picture rental leaseOverview): violates several principles — a long method doing many things (SRP), a switch on a price-code magic constant, and mixing computation with String/HTML formatting (so a new HTML output would force duplication). Improve by extracting amount/points calculation, replacing the switch (polymorphism/enums), and separating computation from formatting.
Refactoring example (PrintPrimes): poor names (single-letter M/RR/CC, JPRIME), magic numbers, and tangled loops mixing prime computation with paged printing — split into well-named methods that compute primes and that format/print pages.
Linters / automatic code review: tools that statically analyze source for bugs, style violations and improvement potential (not every review needs a human). Pros: fast, consistent, cheap, run on every commit; Cons: limited to detectable patterns, false positives, cannot judge design intent — so they complement, not replace, human review (and newer AI-based commit analysis extends this further).
A mix of techniques: regular meetings, calls, chat; mail and a shared wiki; issue trackers (Jira, GitIssues) to discuss, assign and manage work packages, bugs and features.
Document steps, dates and decisions (meeting notes) and communicate problems early; make code and its changes traceable (e.g. include the issue number in branch names 'footer redesign [issue 53]' and commit messages; see Conventional CommitsExplanationConventional Commits is a commit-message convention that uses structured prefixes such as feat, fix, or docs to make change history easier to scan and automate.addition, not part of the lecture).
Example: Linux kernel development uses mailing lists for developer communication.