ЁЯФН рдзрдбрд╛ 09 тАФ Fitness functions: рдХрдзреАрд╣реА рд╕реБрдЯреНрдЯреА рди рдШреЗрдгрд╛рд░реА рдЗрдорд╛рд░рдд-рдирд┐рд░реАрдХреНрд╖рдХ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧрдЪрд╛: lesson-08-data-ownership ┬╖ рдкреБрдврдЪрд╛: lesson-10-c4
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ рдЕрдВрдорд▓рдмрдЬрд╛рд╡рдгреА. рдЖрдзреАрдЪреНрдпрд╛ рдзрдбреНрдпрд╛рдВрдордзрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдирд┐рдпрдо рдЖрддрд╛ рдкреНрд░рддреНрдпреЗрдХ push рд╡рд░ рдЪрд╛рд▓рдгрд╛рд░реА
architecture test рдмрдирддреЛ тАФ рдПрдХ fitness function. рдирд╡реАрди рдорд╛рд░реНрдЧрд┐рдХрд╛ рдЖрд░рд╛рдЦрдбрд╛ рдореЛрдбрддреЗ рддреЗрд╡реНрд╣рд╛ build
fail рд╣реЛрддреЛ, рдЬрд╕рд╛ unit test рдореЛрдбрд▓реНрдпрд╛рд╡рд░ рд╣реЛрддреЛ. fitness() рдЖрдгрд┐ check command
arch/analyze.py рдордзреНрдпреЗ, рдЖрдгрд┐ fitness arch/demo.py рдордзреНрдпреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдХрддрд░рд┐рдирд╛рдиреЗ рдПрдХ рд╕реБрдВрджрд░ рдЗрдорд╛рд░рддреАрдЪрд╛ рдирдХрд╛рд╢рд╛ рдХрд╛рдврд▓рд╛. рдордЧ рдмрд╛рдВрдзрдХрд╛рдо рдХрд░рдгрд╛рд░реЗ рдЖрд▓реЗ. ЁЯС╖тАНтЩАя╕П рддреНрдпрд╛рдВрдЪреНрдпрд╛рдкреИрдХреА рдПрдХ рдШрд╛рдИрдд рд╣реЛрддрд╛ рдЖрдгрд┐ рдмрдВрдж рд░рд╛рд╣рд╛рдпрд▓рд╛ рд╣рд╡реА рд╣реЛрддреА рдЕрд╢рд╛ рднрд┐рдВрддреАрддреВрди рддреНрдпрд╛рдиреЗ рджрд╛рд░ рдлреЛрдбрд▓реЗ. рдХреЛрдгрд╛рдЪреНрдпрд╛рдЪ рд▓рдХреНрд╖рд╛рдд рдЖрд▓реЗ рдирд╛рд╣реА. рд╕рд╣рд╛ рдорд╣рд┐рдиреНрдпрд╛рдВрдиреА рдЕрд╢реА рджрд╣рд╛ рджрд╛рд░реЗ рдЭрд╛рд▓реА, рдЖрдгрд┐ рднрд┐рдВрддреАрд╡рд░рдЪреНрдпрд╛ рдирдХрд╛рд╢рд╛рдЪрд╛ рдЗрдорд╛рд░рддреАрд╢реА рдХрд╛рд╣реАрдЪ рд╕рдВрдмрдВрдз рдЙрд░рд▓рд╛ рдирд╛рд╣реА.
рдореНрд╣рдгреВрди рдРрд╢реНрд╡рд░реНрдпрд╛ рдЗрдорд╛рд░рдд-рдирд┐рд░реАрдХреНрд╖рдХ рдмрдирддреЗ. ЁЯУЛ рддрд┐рдЪреНрдпрд╛рдХрдбреЗ рдПрдХ рдпрд╛рджреА рдЖрд╣реЗ: "рдирд┐рдпрдордкреБрд╕реНрддрд┐рдХреЗрдЪреНрдпрд╛ рдЦреЛрд▓реАрддреВрди рднрд╛рдВрдбрд╛рд░ рдЦреЛрд▓реАрдХрдбреЗ рджрд╛рд░ рдирд╛рд╣реА", "рджреЛрдиреНрд╣реА рджрд┐рд╢рд╛рдВрдирд╛ рдЬрд╛рдгрд╛рд░реЗ рдЬрд┐рдиреЗ рдирд╛рд╣реАрдд", "рд╡рд┐рднрд╛рдЧ рдлрдХреНрдд рдЖрдкрд╛рдкрд▓реНрдпрд╛ рдЦрд┐рдбрдХреНрдпрд╛рдВрдордзреВрдирдЪ рдмреЛрд▓рддрд╛рдд". рдЖрдгрд┐ рд╕рдЧрд│реНрдпрд╛рдд рдЫрд╛рди рдЧреЛрд╖реНрдЯ: рддреА рджрд░рд░реЛрдЬ, рдЖрдкреЛрдЖрдк рддрдкрд╛рд╕рддреЗ, рдХреЛрдгрддреЗрд╣реА рдирд╡реАрди рджрд╛рд░ рдЯрд┐рдХреВ рджреЗрдгреНрдпрд╛рдЖрдзреА.
рдирд╡реАрди shortcut? рдРрд╢реНрд╡рд░реНрдпрд╛рдЪреА рдпрд╛рджреА рд▓рд╛рд▓ рд╣реЛрддреЗ, рдЖрдгрд┐ рдХреЛрдгреА рддреЗ рджреБрд░реБрд╕реНрдд рдХрд░реЗрдкрд░реНрдпрдВрдд тАФ рдХрд┐рдВрд╡рд╛ рд╣реЗрддреБрдкреВрд░реНрд╡рдХ рдЖрд░рд╛рдЦрдбрд╛ рдмрджрд▓реЗрдкрд░реНрдпрдВрдд тАФ рддреЗ рджрд╛рд░ рд╕реНрд╡реАрдХрд╛рд░рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реА.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
push["ЁЯСйтАНЁЯТ╗ git push"] --> ci["тЪЩя╕П CI"]
ci --> unit["ЁЯзк unit tests"]
ci --> fit["ЁЯФН fitness functions<br/>python3 arch/analyze.py check"]
fit --> r1["тЭМ no import cycles"]
fit --> r2["тЭМ dependencies point inward"]
fit --> r3["тЭМ domain imports only the domain"]
fit --> r4["тЭМ departments apart"]
fit --> r5["тЬЕ fan-out тЙд 5"]
fit --> r6["тЬЕ domain instability тЙд 0.5"]
r1 --> fail["exit code 1: the merge is blocked"]
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/software-architecture/lesson-diagrams.html#l09
тЭУ рдХрд╛рдп
- Fitness function тАФ system architecture рдЪреНрдпрд╛ рдЙрджреНрджрд┐рд╖реНрдЯрд╛рдЪреНрдпрд╛ рдХрд┐рддреА рдЬрд╡рд│ рдЖрд╣реЗ рд╣реЗ рд╕рд╛рдВрдЧрдгрд╛рд░реА рдХреЛрдгрддреАрд╣реА рд╡рд╕реНрддреБрдирд┐рд╖реНрда, рдЖрдкреЛрдЖрдк рдЪрд╛рд▓рдгрд╛рд░реА рддрдкрд╛рд╕рдгреА. рд╣рд╛ рд╢рдмреНрдж Building Evolutionary Architectures (Neal Ford, Rebecca Parsons, Patrick Kua; 2017, рджреБрд╕рд░реА рдЖрд╡реГрддреНрддреА 2022 рдордзреНрдпреЗ Pramod Sadalage рдпрд╛рдВрдЪреНрдпрд╛рд╕рд╣) рдордзреВрди рдЖрд▓рд╛ рдЖрд╣реЗ, рдЖрдгрд┐ рддреЛ evolutionary computing рдордзреВрди рдШреЗрддрд▓реЗрд▓рд╛ рдЖрд╣реЗ.
- Architecture test тАФ рд░рдЪрдиреЗрдмрджреНрджрд▓рдЪреА fitness function: import рдирд┐рдпрдо, layers, cycles, рдирд╛рд╡реЗ. рддреА unit test рд╕рд╛рд░рдЦреА рдЪрд╛рд▓рддреЗ рдЖрдгрд┐ build fail рдХрд░рддреЗ.
- Atomic рд╡рд┐рд░реБрджреНрдз holistic тАФ atomic рддрдкрд╛рд╕рдгреА рдПрдХрдЪ рдЧреЛрд╖реНрдЯ рдкрд╛рд╣рддреЗ (cycles рдирд╛рд╣реАрдд); holistic рдЕрдиреЗрдХ рдЧреЛрд╖реНрдЯреА рдПрдХрддреНрд░ рдХрд░рддреЗ (latency рдЖрдгрд┐ error rate рдПрдХрддреНрд░ рддрдкрд╛рд╕рдгрд╛рд░реА load test).
- Triggered рд╡рд┐рд░реБрджреНрдз continual тАФ triggered рдПрдЦрд╛рджреНрдпрд╛ рдШрдЯрдиреЗрд╡рд░ рдЪрд╛рд▓рддреЗ (рдкреНрд░рддреНрдпреЗрдХ push); continual production рдордзреНрдпреЗ рд╕рддрдд рдЪрд╛рд▓рддреЗ (p99 latency рд╡рд░рдЪрд╛ monitor). рдпрд╛ рдзрдбреНрдпрд╛рддреАрд▓ рддрдкрд╛рд╕рдгреНрдпрд╛ triggered рдЖрд╣реЗрдд.
- Threshold тАФ metric рдЬреНрдпрд╛рдЪреНрдпрд╛ рдЦрд╛рд▓реА рд░рд╛рд╣рд╛рдпрд▓рд╛рдЪ рд╣рд╡рд╛ рдЕрд╢реА рдорд░реНрдпрд╛рджрд╛: fan-out тЙд 5, domain instability тЙд 0.5. рдЖрдЬрдЪреНрдпрд╛ рдЖрдХрдбреНрдпрд╛рдВрд╡рд░реВрди рддреНрдпрд╛ рдирд┐рд╡рдбрд╛ рдЖрдгрд┐ рдХрд╛рд▓рд╛рдВрддрд░рд╛рдиреЗ рдШрдЯреНрдЯ рдХрд░рд╛ (рдПрдХ "ratchet").
- Exception list тАФ рдорд╛рд╣реАрдд рдЕрд╕рд▓реЗрд▓реА рдЙрд▓реНрд▓рдВрдШрдиреЗ рд╣реЗрддреБрдкреВрд░реНрд╡рдХ рд▓рд┐рд╣реВрди рдареЗрд╡рд▓реЗрд▓реА (import-linter рдЪреЗ
ignore_imports), рдореНрд╣рдгрдЬреЗ рддрдкрд╛рд╕рдгреА рдЖрдЬ рд╣рд┐рд░рд╡реА рд╣реЛрдК рд╢рдХрддреЗ рдЖрдгрд┐ рддрд░реАрд╣реА рдирд╡реАрди рдЙрд▓реНрд▓рдВрдШрдиреЗ рдерд╛рдВрдмрд╡рддреЗ.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг architecture рдПрдХрд╛ рд╡реЗрд│реА рдПрдХ, рдпреЛрдЧреНрдп рд╡рд╛рдЯрдгрд╛рд▒реНрдпрд╛ import рдиреЗ рдЭрд┐рдЬрддреЗ, рдЖрдгрд┐ code review рд▓рд╛ 3 рдУрд│реАрдВрдЪреНрдпрд╛ diff рдордзреНрдпреЗ рд░рдЪрдиреЗрдЪрд╛ рдирд┐рдпрдо рджрд┐рд╕реВ рд╢рдХрдд рдирд╛рд╣реА. рдХреЛрдгреАрдЪ рди рддрдкрд╛рд╕рдгрд╛рд░рд╛ рдЖрд░рд╛рдЦрдбрд╛ рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд рдЗрдЪреНрдЫрд╛. Build fail рдХрд░рдгрд╛рд░рд╛ рдЖрд░рд╛рдЦрдбрд╛ рдореНрд╣рдгрдЬреЗ рдЯрд┐рдХреВрди рд░рд╛рд╣рдгрд╛рд░рд╛ рдирд┐рд░реНрдгрдп тАФ рдЖрдгрд┐ рдирд┐рдпрдордЪ рдЪреБрдХреАрдЪрд╛ рдЕрд╕реЗрд▓, рддрд░ рддреЛ рдмрджрд▓рдгреЗ рд╣рд╛ рд╢рд╛рдВрддрдкрдгреЗ рд╣реЛрдгрд╛рд░рд╛ drift рди рд░рд╛рд╣рддрд╛ рджрд┐рд╕рдгрд╛рд░рд╛, review рдЭрд╛рд▓реЗрд▓рд╛ рдмрджрд▓ рдард░рддреЛ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
arch/analyze.py рдордзрд▓реЗ fitness(g, classes, max_fan_out=5, max_core_instability=0.5)
рд╕рд╣рд╛ рдирд┐рдпрдо рдЪрд╛рд▓рд╡рддреЗ, рдкреНрд░рддреНрдпреЗрдХ рдЖрдзреАрдЪреНрдпрд╛ рдзрдбреНрдпрд╛рддреВрди рдмрдирд▓реЗрд▓рд╛: cycles() (рдзрдбрд╛ 02/12), ring_violations() рдЖрдгрд┐
core_leaks() (рдзрдбрд╛ 04), context_edges() (рдзрдбрд╛ 05), рдкреНрд░рддреНрдпреЗрдХ рдЦреЛрд▓реАрдЪрд╛ рд╕рд░реНрд╡рд╛рдд рдореЛрдард╛ fan-out (рдзрдбрд╛ 02),
рдЖрдгрд┐ domain рдЪреА instability I (рдзрдбрд╛ 02). рдкреНрд░рддреНрдпреЗрдХ рдирд┐рдпрдо (name, passed, detail) рдкрд░рдд рдХрд░рддреЛ.
python3 arch/analyze.py check рддреЗ рдЫрд╛рдкрддреЗ рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА fail рдЭрд╛рд▓рд╛ рддрд░ exit 1 рдХрд░рддреЗ тАФ CI step рд▓рд╛
рдиреЗрдордХреЗ рд╣реЗрдЪ рд╣рд╡реЗ рдЕрд╕рддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 arch/demo.py fitness
python3 arch/analyze.py check; echo "exit code $?"
python3 - <<'EOF'
import sys; sys.path.insert(0, "arch"); from analyze import scan, apply, fitness, FIXES
cb = scan(); fixed = apply(cb.imports, remove=FIXES)
oops = apply(fixed, add=[("domain.fees", "infra.db")]) # a new shortcut, pushed on a Friday
for name, ok, detail in fitness(oops, cb.classes):
print(f"{'PASS' if ok else 'FAIL'} {name:<40} {detail}")
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
fitness рд╣реЗ рдЫрд╛рдкрддреЗ:
тФАтФА the fitness functions run on every push (python3 arch/analyze.py check)
FAIL no import cycles 1 cycle(s): domain.grades тЖФ infra.db
FAIL dependencies point inward 3 outward import(s)
FAIL the domain imports only the domain 2 leak(s)
FAIL departments do not import each other 2 cross-context import(s)
PASS fan-out тЙд 5 per room (main excluded) max 4 (web.views)
PASS domain instability тЙд 0.5 I = 0.29
after removing the 5 planted corridors: 6/6 pass ┬╖ CI exit code 0
check command рдЪрд╛ рд╢реЗрд╡рдЯ рдЕрд╕рд╛ рд╣реЛрддреЛ:
4 rule(s) failed
exit code 1
рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
PASS no import cycles 0 cycle(s)
FAIL dependencies point inward 1 outward import(s)
FAIL the domain imports only the domain 1 leak(s)
FAIL departments do not import each other 1 cross-context import(s)
PASS fan-out тЙд 5 per room (main excluded) max 4 (web.views)
PASS domain instability тЙд 0.5 I = 0.17
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рджрд┐рд▓реЗрд▓реА рдЗрдорд╛рд░рдд 6 рдкреИрдХреА 4 рдирд┐рдпрдорд╛рдВрдд fail рд╣реЛрддреЗ рдЖрдгрд┐ рддрдкрд╛рд╕рдгреА exit 1 рдХрд░рддреЗ тАФ CI job merge рдерд╛рдВрдмрд╡реЗрд▓.
рдкреЗрд░рд▓реЗрд▓реНрдпрд╛ 5 рдорд╛рд░реНрдЧрд┐рдХрд╛ рдХрд╛рдврд▓реНрдпрд╛рд╡рд░ рд╕рд░реНрд╡ 6 pass рд╣реЛрддрд╛рдд рдЖрдгрд┐ рддреА exit 0 рдХрд░рддреЗ. рдордЧ рд╢реБрдХреНрд░рд╡рд╛рд░рдЪрд╛ рдПрдХ shortcut тАФ
domain.fees рдиреЗ infra.db import рдХрд░рдгреЗ тАФ рддреАрди рдирд┐рдпрдорд╛рдВрдиреА рдПрдХрд╛рдЪ рд╡реЗрд│реА рдкрдХрдбрд▓рд╛ рдЬрд╛рддреЛ: рддреЛ рдмрд╛рд╣реЗрд░рдЪреНрдпрд╛ рджрд┐рд╢реЗрдиреЗ
рдЬрд╛рддреЛ, рддреЛ domain рдЧрд│реВ рджреЗрддреЛ, рдЖрдгрд┐ рддреЛ fees рд╡рд┐рднрд╛рдЧрд╛рддреВрди grades рдЪреНрдпрд╛ рдорд╛рд▓рдХреАрдЪреНрдпрд╛ рдЦреЛрд▓реАрдд рдЬрд╛рддреЛ. рдХрд╢рд╛рдиреЗ рддреЛ
рдкрдХрдбрд▓рд╛ рдирд╛рд╣реА рддреЗ рдкрд╛рд╣рд╛: domain рдЪреА instability рдлрдХреНрдд 0.00 рд╡рд░реВрди 0.17 рдЭрд╛рд▓реА, рдЕрдЬреВрдирд╣реА 0.5 threshold рдЪреНрдпрд╛
рдЦрд╛рд▓реА. Metric thresholds рдвреЛрдмрд│ рдЕрд╕рддрд╛рдд; рд╕реНрдкрд╖реНрдЯ рдирд┐рдпрдо рдзрд╛рд░рджрд╛рд░ рдЕрд╕рддрд╛рдд. рджреЛрдиреНрд╣реА рд╡рд╛рдкрд░рд╛.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- рдЬреБрдиреНрдпрд╛ codebase рд▓рд╛ exception list рд╢рд┐рд╡рд╛рдп рдХрдбрдХ рдирд┐рдпрдо рд▓рд╛рд╡рдгреЗ тЖТ рддрдкрд╛рд╕рдгреА рдХрд╛рдпрдордЪреА рд▓рд╛рд▓ рд░рд╛рд╣рддреЗ рдЖрдгрд┐ рдмрдВрдж рдХреЗрд▓реА рдЬрд╛рддреЗ
- рдлрдХреНрдд wiki page рд╡рд░ рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ рдирд┐рдпрдо
- рдХреЛрдгреАрдЪ рдХрдзреА рдШрдЯреНрдЯ рди рдХрд░рдгрд╛рд░реЗ thresholds
- рдирд┐рдпрдо рдлрдХреНрдд happy path рд╡рд░ test рдХрд░рдгреЗ тАФ рдкреЗрд░рд▓реЗрд▓реНрдпрд╛ рдЙрд▓реНрд▓рдВрдШрдирд╛рд╡рд░ рддреЛ fail рд╣реЛрддреЛ рдпрд╛рдЪреА рдЦрд╛рддреНрд░реА рдХрд░рд╛ (snippet рдХрд░рддреЛ рддрд╕реЗ)
- pull-request check рдордзреНрдпреЗ рди рдареЗрд╡рддрд╛, рдХреЛрдгреАрдЪ рди рдкрд╛рд╣рдгрд╛рд▒реНрдпрд╛ рд╡реЗрдЧрд│реНрдпрд╛ nightly job рдордзреНрдпреЗ architecture checks
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ project рд╡рд░ тАФ checks unit tests рдЪреНрдпрд╛рдЪ pipeline рдордзреНрдпреЗ рдЪрд╛рд▓рд╡рд╛. GitHub Actions:
name: architecture
on: [push, pull_request]
jobs:
fitness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python3 arch/analyze.py check
- run: pip install import-linter && lint-imports
рдЗрддрд░ рднрд╛рд╖рд╛рдВрдордзреНрдпреЗ рд╣реАрдЪ рдХрд▓реНрдкрдирд╛:
- Java: ArchUnit тАФ
noClasses().that().resideInAPackage("..domain..").should().dependOnClassesThat().resideInAPackage("..infra.."), рдЖрдгрд┐slices().matching("..school.(*)..").should().beFreeOfCycles() - JavaScript / TypeScript: dependency-cruiser тАФ
no-circularрдирд┐рдпрдорд╛рд╕рд╣npx depcruise src --config .dependency-cruiser.js - .NET: NetArchTest тАФ
Types.InAssembly(asm).That().ResideInNamespace("School.Domain").ShouldNot().HaveDependencyOn("School.Infra").GetResult()
Exception list рдореБрд│реЗ рддреБрдореНрд╣реА рдирд┐рдпрдо рдЖрдЬрдЪ рдЪрд╛рд▓реВ рдХрд░реВ рд╢рдХрддрд╛ (import-linter):
[importlinter:contract:core]
name = The domain imports no adapters
type = forbidden
source_modules = schoolapp.domain
forbidden_modules = schoolapp.infra
# a known violation, ticket SCH-142 тАФ the list may shrink, never grow
ignore_imports =
schoolapp.domain.grades -> schoolapp.infra.db
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рддреБрдордЪреЗ рд░рдЪрдиреЗрдЪреЗ рджреЛрди рд╕рд░реНрд╡рд╛рдд рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдирд┐рдпрдо рдирд┐рд╡рдбрд╛, рдпрд╛ рдЖрдард╡рдбреНрдпрд╛рдд рддреЗ tests рдореНрд╣рдгреВрди рд▓рд┐рд╣рд╛, рдЖрдгрд┐ рдПрдХ рдЙрд▓реНрд▓рдВрдШрди рдкреЗрд░реВрди рддреЗ fail рд╣реЛрддрд╛рдирд╛ рдкрд╛рд╣рд╛. рдЬреЛ рдирд┐рдпрдо рддреБрдореНрд╣реА рдХрдзреА fail рд╣реЛрддрд╛рдирд╛ рдкрд╛рд╣рд┐рд▓реЗрд▓рд╛ рдирд╛рд╣реА, рддреЛ рдЪрд╛рд▓рддреЛ рдХреА рдирд╛рд╣реА рд╣реЗ рддреБрдореНрд╣рд╛рд▓рд╛ рдорд╛рд╣реАрдд рдирд╕рддреЗ.
тПня╕П рдкреБрдвреЗ
рдирд┐рд░реАрдХреНрд╖рдХ рдЗрдорд╛рд░рдд рддрдкрд╛рд╕рддреЗ. рдЖрддрд╛ рддреА рдХрд╛рдврд╛ тАФ code рдордзреВрдирдЪ, рдореНрд╣рдгрдЬреЗ рдЪрд┐рддреНрд░ рдЦреЛрдЯреЗ рдмреЛрд▓реВ рд╢рдХрдд рдирд╛рд╣реА. C4 model.
git checkout lesson-10-c4