summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
author2026-01-31 20:38:50 +0300
committer2026-01-31 23:38:53 +0300
commit49458f5ffd5a48c465117ec27f6437683f75acc1 (patch)
treea99ee68116d10c2b2e5a70c442cdadec95ba793c /content
downloadblog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.gz
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.bz2
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.xz
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.zip
initial
Diffstat (limited to '')
-rw-r--r--content/_index.md25
-rw-r--r--content/books/index.md6
-rw-r--r--content/books/under-construction90s.gifbin0 -> 3541 bytes
-rw-r--r--content/me/index.md37
-rw-r--r--content/pages/_index.md4
-rw-r--r--content/pages/archive/2007-11-05.md3
-rw-r--r--content/pages/archive/2007-11-06.md3
-rw-r--r--content/pages/archive/2007-11-08.md3
-rw-r--r--content/pages/archive/2007-12-11.md3
-rw-r--r--content/pages/archive/2007-12-26.md3
-rw-r--r--content/pages/archive/2011-05-10.md3
-rw-r--r--content/pages/archive/2011-09-11-1.md3
-rw-r--r--content/pages/archive/2011-09-11-2.md3
-rw-r--r--content/pages/archive/2011-09-11-3.md3
-rw-r--r--content/pages/archive/2011-09-12.md3
-rw-r--r--content/pages/archive/2011-09-24.md3
-rw-r--r--content/pages/archive/2011-11-20.md3
-rw-r--r--content/pages/archive/2011-11-21.md3
-rw-r--r--content/pages/archive/_index.md4
-rw-r--r--content/pages/gostyleguide/_index.md6
-rw-r--r--content/pages/gostyleguide/google/_index.md10
-rw-r--r--content/pages/gostyleguide/google/best-practices.md3727
-rw-r--r--content/pages/gostyleguide/google/decisions.md4057
-rw-r--r--content/pages/gostyleguide/google/guide.md495
-rw-r--r--content/pages/gostyleguide/google/main.md184
-rw-r--r--content/pages/gostyleguide/uber/index.md4140
-rw-r--r--content/pages/setup/_index.md6
-rw-r--r--content/pages/setup/laptop.md36
-rw-r--r--content/pages/setup/nas.md32
-rw-r--r--content/pages/setup/pda.md38
-rw-r--r--content/posts/2021-02-13-jsonnet.md101
-rw-r--r--content/posts/2021-05-13-цифровая-гигиена.md91
-rw-r--r--content/posts/2022-05-30-возрождение.md27
-rw-r--r--content/posts/2022-05-31-golang-1.md44
-rw-r--r--content/posts/2023-01-12-gitrepo.md167
-rw-r--r--content/posts/2023-05-26-gist.md19
-rw-r--r--content/posts/2023-07-24-tls.md85
-rw-r--r--content/posts/2023-12-29-переезд.md28
-rw-r--r--content/posts/2024-01-03-архив.md14
-rw-r--r--content/posts/2024-02-21-tls.md54
-rw-r--r--content/posts/2024-06-01-вам-не-нужны-пуши.md110
-rw-r--r--content/posts/2024-06-02-книги-1.md21
-rw-r--r--content/posts/2024-07-13-joplin.md43
-rw-r--r--content/posts/2024-07-21-bbs.md33
-rw-r--r--content/posts/2024-09-26-hugo-wordpress.md16
-rw-r--r--content/posts/2024-10-06-цитатник-рунета.md64
-rw-r--r--content/posts/2024-10-17-книги-2.md39
-rw-r--r--content/posts/2024-11-15-hugo.md19
-rw-r--r--content/posts/2024-11-17-obsidian.md240
-rw-r--r--content/posts/2024-11-27-hyperlocality.md182
-rw-r--r--content/posts/2024-11-29-hobbies.md44
-rw-r--r--content/posts/2024-12-12-guessr.md130
-rw-r--r--content/posts/2024-12-15-conditional-operator-go.md35
-rw-r--r--content/posts/2024-12-15-posse.md81
-rw-r--r--content/posts/2024-12-17-infra.md120
-rw-r--r--content/posts/2024-12-30-irc.md75
-rw-r--r--content/posts/2024-12-31-new-year.md50
-rw-r--r--content/posts/2025-04-05-tabs-or-spaces.md400
-rw-r--r--content/posts/2025-05-19-nxpcms-2.md19
-rw-r--r--content/posts/2025-05-19-nxpcms.md77
-rw-r--r--content/posts/2025-06-08-my-setup.md31
-rw-r--r--content/posts/2025-08-02-meshtastic.md66
-rw-r--r--content/posts/2025-08-05-lets-code-3d.md154
-rw-r--r--content/posts/2025-08-09-makeup-organizer.md38
-rw-r--r--content/posts/2025-09-01-travel-1.md142
-rw-r--r--content/posts/2025-10-06-ai.md124
-rw-r--r--content/posts/2025-10-11-blog.md32
-rw-r--r--content/posts/2025-10-18-the-ghost-in-the-machine.md184
-rw-r--r--content/posts/2025-11-03-blog-deploy.md46
-rw-r--r--content/posts/2025-11-03-my-setup.md21
-rw-r--r--content/posts/2025-11-04-blog-deploy-2.md50
-rw-r--r--content/posts/2025-11-09-migration.md55
-rw-r--r--content/posts/2025-11-23-org.md253
-rw-r--r--content/posts/2025-12-02-httpsocalypse.md31
-rw-r--r--content/posts/2025-12-21-sicktech.md228
-rw-r--r--content/posts/2025-12-23-comments.md12
-rw-r--r--content/posts/2025-12-24-email.md99
-rw-r--r--content/posts/2025-12-27-osm.md40
-rw-r--r--content/posts/2025-12-28-philharmonic-park.md32
-rw-r--r--content/posts/2025-12-29-newyear-excel.md22
-rw-r--r--content/posts/2025-12-31-new-year.md28
-rw-r--r--content/posts/2025-12-31-qchat.md46
-rw-r--r--content/posts/_index.md8
-rw-r--r--content/posts/files/2021-02-13-jsonnet_logo.webpbin0 -> 47006 bytes
-rw-r--r--content/posts/files/2024-07-13-joplin_joplin.webpbin0 -> 111506 bytes
-rw-r--r--content/posts/files/2024-10-06-цитатник-рунета_bash_org.webpbin0 -> 20774 bytes
-rw-r--r--content/posts/files/2024-10-17-книги-2_Rama16wiki.webpbin0 -> 44726 bytes
-rw-r--r--content/posts/files/2024-11-17-obsidian_img/logo.webpbin0 -> 9244 bytes
-rw-r--r--content/posts/files/2024-11-17-obsidian_img/publish.webpbin0 -> 23110 bytes
-rw-r--r--content/posts/files/2024-11-17-obsidian_img/templater.webpbin0 -> 17140 bytes
-rw-r--r--content/posts/files/2024-11-27-hyperlocality_img/90e.webpbin0 -> 71910 bytes
-rw-r--r--content/posts/files/2024-11-27-hyperlocality_img/braindance.webpbin0 -> 92740 bytes
-rw-r--r--content/posts/files/2024-11-27-hyperlocality_img/camp.webpbin0 -> 115704 bytes
-rw-r--r--content/posts/files/2024-11-27-hyperlocality_img/in-internet.webpbin0 -> 136578 bytes
-rw-r--r--content/posts/files/2024-11-29-hobbies_dozor.webpbin0 -> 101246 bytes
-rw-r--r--content/posts/files/2024-12-12-guessr_logo.webpbin0 -> 112508 bytes
-rw-r--r--content/posts/files/2024-12-15-conditional-operator-go_ternary.webpbin0 -> 7408 bytes
-rw-r--r--content/posts/files/2024-12-15-posse_posse.webpbin0 -> 60800 bytes
-rw-r--r--content/posts/files/2024-12-17-infra_cover.webpbin0 -> 532556 bytes
-rw-r--r--content/posts/files/2024-12-30-irc_logo.webpbin0 -> 10130 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/1.webpbin0 -> 141164 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/2.webpbin0 -> 212740 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/2025.webpbin0 -> 163920 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/3.webpbin0 -> 446502 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/4.webpbin0 -> 146032 bytes
-rw-r--r--content/posts/files/2024-12-31-new-year_img/5.webpbin0 -> 190524 bytes
-rw-r--r--content/posts/files/2025-12-21-img1.jpgbin0 -> 162511 bytes
-rw-r--r--content/posts/files/2025-12-21-img2.jpgbin0 -> 88801 bytes
-rw-r--r--content/posts/files/2025-12-21-img3.pngbin0 -> 340974 bytes
-rw-r--r--content/posts/files/2025-12-21-sicktech.pngbin0 -> 263578 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo.jpgbin0 -> 227501 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_1.jpgbin0 -> 221795 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_10.jpgbin0 -> 127547 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_11.jpgbin0 -> 151264 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_12.jpgbin0 -> 121963 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_13.jpgbin0 -> 126052 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_14.jpgbin0 -> 225031 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_2.jpgbin0 -> 226528 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_3.jpgbin0 -> 234383 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_4.jpgbin0 -> 214772 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_5.jpgbin0 -> 179943 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_6.jpgbin0 -> 168080 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_7.jpgbin0 -> 161387 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_8.jpgbin0 -> 127482 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/photo_9.jpgbin0 -> 213571 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo.jpgbin0 -> 11353 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_1.jpgbin0 -> 14146 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_10.jpgbin0 -> 9230 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_11.jpgbin0 -> 10581 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_12.jpgbin0 -> 13124 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_13.jpgbin0 -> 14557 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_14.jpgbin0 -> 15019 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_2.jpgbin0 -> 15041 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_3.jpgbin0 -> 15339 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_4.jpgbin0 -> 14214 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_5.jpgbin0 -> 12279 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_6.jpgbin0 -> 10936 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_7.jpgbin0 -> 15840 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_8.jpgbin0 -> 11695 bytes
-rw-r--r--content/posts/files/2025-12-27-osm_img/thumbs/photo_9.jpgbin0 -> 15837 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_19_2025-12-28_21-35-40.jpgbin0 -> 307509 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_1_2025-12-28_21-35-40.jpgbin0 -> 284188 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_20_2025-12-28_21-35-40.jpgbin0 -> 253734 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_25_2025-12-28_21-35-40.jpgbin0 -> 242625 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_27_2025-12-28_21-35-40.jpgbin0 -> 331494 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_2_2025-12-28_21-35-40.pngbin0 -> 1966164 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_30_2025-12-28_21-35-40.jpgbin0 -> 352290 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_31_2025-12-28_21-35-40.jpgbin0 -> 216175 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.pngbin0 -> 1423681 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_3_2025-12-28_21-35-40.pngbin0 -> 1487705 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_5_2025-12-28_21-35-40.jpgbin0 -> 294826 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/photo_9_2025-12-28_21-35-40.jpgbin0 -> 183104 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_19_2025-12-28_21-35-40.jpgbin0 -> 18056 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_1_2025-12-28_21-35-40.jpgbin0 -> 12003 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_20_2025-12-28_21-35-40.jpgbin0 -> 17032 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_25_2025-12-28_21-35-40.jpgbin0 -> 17446 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_27_2025-12-28_21-35-40.jpgbin0 -> 16794 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_2_2025-12-28_21-35-40.jpgbin0 -> 29912 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_30_2025-12-28_21-35-40.jpgbin0 -> 21623 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpgbin0 -> 10522 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_32_2025-12-28_21-35-40.jpgbin0 -> 14036 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_3_2025-12-28_21-35-40.jpgbin0 -> 25867 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_5_2025-12-28_21-35-40.jpgbin0 -> 18394 bytes
-rw-r--r--content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_9_2025-12-28_21-35-40.jpgbin0 -> 15546 bytes
-rw-r--r--content/posts/files/2025-12-29-newyear-excel_img/cover.pngbin0 -> 46582 bytes
-rw-r--r--content/posts/files/2025-12-29-newyear-excel_img/cover1.pngbin0 -> 14651 bytes
-rw-r--r--content/posts/files/2025-12-29-newyear-excel_img/new_year.odsbin0 -> 28669 bytes
-rw-r--r--content/posts/files/2025-12-29-newyear-excel_img/new_year.xlsxbin0 -> 7859 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/1.webpbin0 -> 1307382 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/10.webpbin0 -> 1024096 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/11.webpbin0 -> 1031876 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/12.webpbin0 -> 373748 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/13.webpbin0 -> 574476 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/14.webpbin0 -> 385098 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/15.webpbin0 -> 381654 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/16.webpbin0 -> 2601414 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/17.webpbin0 -> 3254752 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/18.webpbin0 -> 1640240 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/19.webpbin0 -> 2578076 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/2.webpbin0 -> 1660606 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/20.webpbin0 -> 3858586 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/21.webpbin0 -> 4400828 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/22.webpbin0 -> 2109710 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/23.webpbin0 -> 988944 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/3.webpbin0 -> 5237948 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/4.webpbin0 -> 1403370 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/5.webpbin0 -> 1424414 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/6.webpbin0 -> 1129282 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/7.webpbin0 -> 1178630 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/8.webpbin0 -> 4411964 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/9.webpbin0 -> 755094 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_1.webpbin0 -> 68140 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_10.webpbin0 -> 119806 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_11.webpbin0 -> 114776 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_12.webpbin0 -> 27904 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_13.webpbin0 -> 36594 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_14.webpbin0 -> 29198 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_15.webpbin0 -> 30840 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_16.webpbin0 -> 83166 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_17.webpbin0 -> 154656 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_18.webpbin0 -> 99452 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_19.webpbin0 -> 94816 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_2.webpbin0 -> 49558 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_20.webpbin0 -> 144248 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_21.webpbin0 -> 179974 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_22.webpbin0 -> 56148 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_23.webpbin0 -> 64300 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_3.webpbin0 -> 162906 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_4.webpbin0 -> 45526 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_5.webpbin0 -> 48660 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_6.webpbin0 -> 52192 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_7.webpbin0 -> 44236 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_8.webpbin0 -> 110076 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/preview_9.webpbin0 -> 83176 bytes
-rw-r--r--content/posts/files/2025-travel-1_img/saologo.pngbin0 -> 20791 bytes
-rw-r--r--content/posts/files/hype_curve.pngbin0 -> 130810 bytes
-rw-r--r--content/posts/files/laughing-man.jpegbin0 -> 234679 bytes
-rw-r--r--content/posts/files/lets-code-3d_img/1.jpgbin0 -> 86020 bytes
-rw-r--r--content/posts/files/lets-code-3d_img/2.jpgbin0 -> 58451 bytes
-rw-r--r--content/posts/files/lets-code-3d_img/3.pngbin0 -> 108360 bytes
-rw-r--r--content/posts/files/lets-code-3d_img/4.jpgbin0 -> 75461 bytes
-rw-r--r--content/posts/files/lets-code-3d_result.stlbin0 -> 3684 bytes
-rw-r--r--content/posts/files/lets-code-3d_source.scad66
-rw-r--r--content/posts/files/makeup-organizer_img/1.pngbin0 -> 180919 bytes
-rw-r--r--content/posts/files/makeup-organizer_img/2.pngbin0 -> 354046 bytes
-rw-r--r--content/posts/files/makeup-organizer_img/3.pngbin0 -> 115406 bytes
-rw-r--r--content/posts/files/makeup-organizer_organizer.tar.zstbin0 -> 660078 bytes
-rw-r--r--content/posts/files/meshtastic_img/tbeam.jpgbin0 -> 269445 bytes
-rw-r--r--content/posts/files/meshtastic_img/tbeam.webpbin0 -> 116662 bytes
-rw-r--r--content/projects/_index.md9
-rw-r--r--content/projects/games/bubblebreaker.p8.pngbin0 -> 9840 bytes
-rw-r--r--content/projects/games/gameof15.p8.pngbin0 -> 5799 bytes
-rw-r--r--content/projects/games/index.md30
-rw-r--r--content/projects/games/lines.p8.pngbin0 -> 6651 bytes
-rw-r--r--content/projects/games/snake.p8.pngbin0 -> 6229 bytes
-rw-r--r--content/projects/qchat.md89
236 files changed, 17210 insertions, 0 deletions
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..85bb929
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,25 @@
+---
+title: Добро пожаловать!
+---
+
+<img src="/files/logo512.webp" align="right" width="256" />
+
+Добро пожаловать на мой личный сервер.
+
+Да, это старая добрая домашняя страница, персональный сайт, «хомяк», называйте как привычнее.
+
+Меня зовут Саня, я Go разработчик. Остальное обо мне на [отдельной странице](/me/).
+
+А ещё, у меня есть свой [Блог](/posts/)!
+
+И у него даже есть [Atom лента](/feed/posts/) для удобной подписки!
+
+Для связи со мной — пишите на e-mail или jabber [i@neonxp.ru](mailto:i@neonxp.ru)
+
+Другие форматы блога:
+
+- Telegram канал: https://t.me/neonxplog
+- VK паблик: https://vk.com/neonxplog
+- Dzen канал: https://dzen.ru/neonxp
+
+Подписаться можно где угодно, но первоисточник по [POSSE](/posts/2024-12-15-posse/) именно здесь.
diff --git a/content/books/index.md b/content/books/index.md
new file mode 100644
index 0000000..98bed36
--- /dev/null
+++ b/content/books/index.md
@@ -0,0 +1,6 @@
+---
+order: '10'
+title: Я читаю
+---
+TODO
+![TODO](under-construction90s.gif)
diff --git a/content/books/under-construction90s.gif b/content/books/under-construction90s.gif
new file mode 100644
index 0000000..ab87cfc
--- /dev/null
+++ b/content/books/under-construction90s.gif
Binary files differ
diff --git a/content/me/index.md b/content/me/index.md
new file mode 100644
index 0000000..094845b
--- /dev/null
+++ b/content/me/index.md
@@ -0,0 +1,37 @@
+---
+order: "30"
+title: Обо мне...
+---
+
+<!--more-->
+<div class="h-card">
+ <h1 class="p-name">
+ <span class="p-given-cname">Александр</span> <span class="p-nickname">NeonXP</span> <span class="p-family-name">Кирюхин</span>
+ </h1>
+ <ul>
+ <li>E-mail: <a href="mailto:i@neonxp.ru" rel="me" class="u-email">i@neonxp.ru</a></li>
+ <li>Jabber: <a href="xmpp:i@neonxp.ru" rel="me" class="u-jabber">i@neonxp.ru</a></li>
+ <li>PGP: <a href="https://neonxp.ru/files/0x96BF11A67E3C75F6.asc" rel="pgpkey" class="u-key">9E49 0BBE 2F1F 82C9 15F8 F440 96BF 11A6 7E3C 75F6</a></li>
+ <li>
+ <span class="p-locality">Казань</span>,
+ <abbr class="p-region" title="Республика Татарстан">РТ</abbr>,
+ <span class="p-country-name">Российская Федерация</span>
+ </li>
+ <li>
+ <a rel="me" href="https://neonxp.ru/" class="u-url">Веб-сайт</a>
+ </li>
+ <li>
+ <a rel="me" href="https://neonxp.ru/files/vcard.vcf" class="u-url">Моя визитка с RSS</a>
+ </li>
+ <li class="p-note">
+ Golang разработчик
+ </li>
+ </ul>
+ <img src="https://neonxp.ru/files/photo.webp" class="u-photo rounded border shadow" width="600" />
+</div>
+
+# Другие ссылки
+
+- [Мой git](https://gitrepo.ru/neonxp/)
+- [Мои Go пакеты](https://go.neonxp.ru/)
+- [Telegram Канал](https://t.me/neonxplog)
diff --git a/content/pages/_index.md b/content/pages/_index.md
new file mode 100644
index 0000000..3d813e3
--- /dev/null
+++ b/content/pages/_index.md
@@ -0,0 +1,4 @@
+---
+order: '20'
+title: Разное
+---
diff --git a/content/pages/archive/2007-11-05.md b/content/pages/archive/2007-11-05.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2007-11-05.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2007-11-06.md b/content/pages/archive/2007-11-06.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2007-11-06.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2007-11-08.md b/content/pages/archive/2007-11-08.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2007-11-08.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2007-12-11.md b/content/pages/archive/2007-12-11.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2007-12-11.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2007-12-26.md b/content/pages/archive/2007-12-26.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2007-12-26.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-05-10.md b/content/pages/archive/2011-05-10.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-05-10.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-09-11-1.md b/content/pages/archive/2011-09-11-1.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-09-11-1.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-09-11-2.md b/content/pages/archive/2011-09-11-2.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-09-11-2.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-09-11-3.md b/content/pages/archive/2011-09-11-3.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-09-11-3.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-09-12.md b/content/pages/archive/2011-09-12.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-09-12.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-09-24.md b/content/pages/archive/2011-09-24.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-09-24.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-11-20.md b/content/pages/archive/2011-11-20.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-11-20.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/2011-11-21.md b/content/pages/archive/2011-11-21.md
new file mode 100644
index 0000000..ebd0aa3
--- /dev/null
+++ b/content/pages/archive/2011-11-21.md
@@ -0,0 +1,3 @@
+---
+{}
+---
diff --git a/content/pages/archive/_index.md b/content/pages/archive/_index.md
new file mode 100644
index 0000000..2a7cea3
--- /dev/null
+++ b/content/pages/archive/_index.md
@@ -0,0 +1,4 @@
+---
+order: '20'
+title: Архив старых постов из WebArchive
+---
diff --git a/content/pages/gostyleguide/_index.md b/content/pages/gostyleguide/_index.md
new file mode 100644
index 0000000..d38468e
--- /dev/null
+++ b/content/pages/gostyleguide/_index.md
@@ -0,0 +1,6 @@
+---
+order: 10
+title: Go styleguide
+---
+
+Переводы на русский язык двух самых популярных styleguide по Go.
diff --git a/content/pages/gostyleguide/google/_index.md b/content/pages/gostyleguide/google/_index.md
new file mode 100644
index 0000000..f28cb04
--- /dev/null
+++ b/content/pages/gostyleguide/google/_index.md
@@ -0,0 +1,10 @@
+---
+order: 4
+title: Google Go Style Guide
+---
+
+# Стиль Go
+
+Оригинал: https://google.github.io/styleguide/go
+
+<!--more-->
diff --git a/content/pages/gostyleguide/google/best-practices.md b/content/pages/gostyleguide/google/best-practices.md
new file mode 100644
index 0000000..4fc59b1
--- /dev/null
+++ b/content/pages/gostyleguide/google/best-practices.md
@@ -0,0 +1,3727 @@
+---
+order: 1
+title: Google Go Style Guide — Лучшие практики
+---
+
+# Лучшие практики стиля Go (Go Style Best Practices)
+
+Оригинал: https://google.github.io/styleguide/go/best-practices
+
+[Обзор](https://neonxp.ru/pages/gostyleguide/google/) | [Руководство](https://neonxp.ru/pages/gostyleguide/google/guide) | [Решения](https://neonxp.ru/pages/gostyleguide/google/decisions) |
+[Лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices)
+
+**Примечание:** Это часть серии документов, описывающих [Стиль Go (Go
+Style)](https://neonxp.ru/pages/gostyleguide/google/) в Google. Данный документ **не является ни [нормативным
+(normative)](https://neonxp.ru/pages/gostyleguide/google/#normative), ни [каноническим (canonical)](https://neonxp.ru/pages/gostyleguide/google/#canonical)**,
+а является вспомогательным документом к [основному руководству по стилю](https://neonxp.ru/pages/gostyleguide/google/guide/).
+Подробнее см. [в обзоре](https://neonxp.ru/pages/gostyleguide/google/#about).
+
+<a id="about"></a>
+
+## О документе (About)
+
+В этом документе представлены **рекомендации о том, как наилучшим образом
+применять Руководство по стилю Go**. Эти рекомендации предназначены для типичных
+ситуаций, возникающих часто, но могут не применяться в каждом случае. По
+возможности обсуждаются несколько альтернативных подходов, а также соображения,
+которые учитываются при решении о том, когда их применять, а когда нет.
+
+См. [обзор](https://neonxp.ru/pages/gostyleguide/google/#about) для полного набора документов руководства по стилю.
+
+<a id="naming"></a>
+
+## Именование (Naming)
+
+<a id="function-names"></a>
+
+### Имена функций и методов
+
+<a id="function-name-repetition"></a>
+
+#### Избегайте повторения (Avoid repetition)
+
+При выборе имени для функции или метода учитывайте контекст, в котором это имя
+будет прочитано. Рассмотрите следующие рекомендации, чтобы избежать избыточного
+[повторения (repetition)](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetition) в месте вызова (call site):
+
+* Следующее, как правило, можно опустить в именах функций и методов:
+
+ * Типы входных и выходных данных (если нет конфликта)
+ * Тип получателя (receiver) метода
+ * Является ли входной или выходной параметр указателем (pointer)
+
+* Для функций не следует [повторять имя
+ пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetitive-with-package).
+
+ ```go
+ // Плохо:
+ package yamlconfig
+
+ func ParseYAMLConfig(input string) (*Config, error)
+ ```
+
+ ```go
+ // Хорошо:
+ package yamlconfig
+
+ func Parse(input string) (*Config, error)
+ ```
+
+* Для методов не следует повторять имя получателя метода.
+
+ ```go
+ // Плохо:
+ func (c *Config) WriteConfigTo(w io.Writer) (int64, error)
+ ```
+
+ ```go
+ // Хорошо:
+ func (c *Config) WriteTo(w io.Writer) (int64, error)
+ ```
+
+* Не повторяйте имена переменных, передаваемых в качестве параметров.
+
+ ```go
+ // Плохо:
+ func OverrideFirstWithSecond(dest, source *Config) error
+ ```
+
+ ```go
+ // Хорошо:
+ func Override(dest, source *Config) error
+ ```
+
+* Не повторяйте имена и типы возвращаемых значений.
+
+ ```go
+ // Плохо:
+ func TransformToJSON(input *Config) *jsonconfig.Config
+ ```
+
+ ```go
+ // Хорошо:
+ func Transform(input *Config) *jsonconfig.Config
+ ```
+
+Когда необходимо устранить неоднозначность между функциями с похожими именами,
+допустимо включить дополнительную информацию.
+
+```go
+// Хорошо:
+func (c *Config) WriteTextTo(w io.Writer) (int64, error)
+func (c *Config) WriteBinaryTo(w io.Writer) (int64, error)
+```
+
+<a id="function-name-conventions"></a>
+
+#### Соглашения об именовании (Naming conventions)
+
+Существуют и другие общие соглашения при выборе имен для функций и методов:
+
+* Функции, которые что-то возвращают, получают имена, похожие на
+ существительные.
+
+ ```go
+ // Хорошо:
+ func (c *Config) JobName(key string) (value string, ok bool)
+ ```
+
+ Следствием этого является то, что имена функций и методов должны [избегать
+ префикса `Get`](https://neonxp.ru/pages/gostyleguide/google/decisions/#getters).
+
+ ```go
+ // Плохо:
+ func (c *Config) GetJobName(key string) (value string, ok bool)
+ ```
+
+* Функции, которые что-то делают, получают имена, похожие на глаголы.
+
+ ```go
+ // Хорошо:
+ func (c *Config) WriteDetail(w io.Writer) (int64, error)
+ ```
+
+* Идентичные функции, которые отличаются только типами, включают имя типа в
+ конце имени.
+
+ ```go
+ // Хорошо:
+ func ParseInt(input string) (int, error)
+ func ParseInt64(input string) (int64, error)
+ func AppendInt(buf []byte, value int) []byte
+ func AppendInt64(buf []byte, value int64) []byte
+ ```
+
+ Если существует ясная "основная" версия, тип может быть опущен в имени для
+ этой версии:
+
+ ```go
+ // Хорошо:
+ func (c *Config) Marshal() ([]byte, error)
+ func (c *Config) MarshalText() (string, error)
+ ```
+
+<a id="naming-doubles"></a>
+
+### Тестовые дубли (Test doubles) и вспомогательные пакеты (helper packages)
+
+Существует несколько подходов, которые можно применить для [именования] пакетов
+и типов, которые предоставляют тестовые вспомогательные средства и особенно
+[тестовые дубли (test doubles)]. Тестовым дублем может быть заглушка (stub),
+фейк (fake), мок (mock) или шпион (spy).
+
+Эти примеры в основном используют заглушки. Обновите свои имена соответствующим
+образом, если ваш код использует фейки или другой вид тестового дубля.
+
+[именование]: guide#naming
+[тестовые дубли (test doubles)]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+
+Предположим, у вас есть хорошо сфокусированный пакет, предоставляющий
+production-код, подобный этому:
+
+```go
+package creditcard
+
+import (
+ "errors"
+
+ "path/to/money"
+)
+
+// ErrDeclined указывает, что эмитент отклонил операцию.
+var ErrDeclined = errors.New("creditcard: declined")
+
+// Card содержит информацию о кредитной карте, такую как эмитент,
+// срок действия и лимит.
+type Card struct {
+ // опущено
+}
+
+// Service позволяет выполнять операции с кредитными картами через внешние
+// процессинговые системы, такие как списание, авторизация, возврат средств и подписка.
+type Service struct {
+ // опущено
+}
+
+func (s *Service) Charge(c *Card, amount money.Money) error { /* опущено */ }
+```
+
+<a id="naming-doubles-helper-package"></a>
+
+#### Создание вспомогательных тестовых пакетов (Creating test helper packages)
+
+Предположим, вы хотите создать пакет, содержащий тестовые дубли для другого
+пакета. Воспользуемся `package creditcard` (из примера выше):
+
+Один из подходов — создать новый Go-пакет на основе production-пакета для
+тестирования. Безопасный выбор — добавить слово `test` к оригинальному имени
+пакета ("creditcard" + "test"):
+
+```go
+// Хорошо:
+package creditcardtest
+```
+
+Если явно не указано иное, все примеры в следующих разделах находятся в `package
+creditcardtest`.
+
+<a id="naming-doubles-simple"></a>
+
+#### Простой случай (Simple case)
+
+Вы хотите добавить набор тестовых дублей для `Service`. Поскольку `Card` по сути
+является простым типом данных, похожим на сообщение Protocol Buffer, он не
+требует специальной обработки в тестах, поэтому дубль не нужен. Если вы ожидаете
+только тестовые дубли для одного типа (например, `Service`), вы можете
+использовать лаконичный подход к именованию дублей:
+
+```go
+// Хорошо:
+import (
+ "path/to/creditcard"
+ "path/to/money"
+)
+
+// Stub заглушает creditcard.Service и не предоставляет собственного поведения.
+type Stub struct{}
+
+func (Stub) Charge(*creditcard.Card, money.Money) error { return nil }
+```
+
+Это строго предпочтительнее, чем выбор имен типа `StubService` или очень плохого
+`StubCreditCardService`, потому что базовое имя пакета и его доменные типы
+подразумевают, что такое `creditcardtest.Stub`.
+
+Наконец, если пакет собирается с помощью Bazel, убедитесь, что новое правило
+`go_library` для пакета помечено как `testonly`:
+
+```build
+# Хорошо:
+go_library(
+ name = "creditcardtest",
+ srcs = ["creditcardtest.go"],
+ deps = [
+ ":creditcard",
+ ":money",
+ ],
+ testonly = True,
+)
+```
+
+Приведенный выше подход является общепринятым и будет достаточно хорошо понят
+другими инженерами.
+
+См. также:
+
+* [Go Tip #42: Authoring a Stub for
+ Testing](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="naming-doubles-multiple-behaviors"></a>
+
+#### Несколько вариантов поведения тестового дубля (Multiple test double behaviors)
+
+Когда одного вида заглушки недостаточно (например, нужна еще одна, которая
+всегда завершается ошибкой), мы рекомендуем называть заглушки в соответствии с
+поведением, которое они эмулируют. Здесь мы переименовываем `Stub` в
+`AlwaysCharges` и вводим новую заглушку `AlwaysDeclines`:
+
+```go
+// Хорошо:
+// AlwaysCharges заглушает creditcard.Service и имитирует успех.
+type AlwaysCharges struct{}
+
+func (AlwaysCharges) Charge(*creditcard.Card, money.Money) error { return nil }
+
+// AlwaysDeclines заглушает creditcard.Service и имитирует отклоненные операции.
+type AlwaysDeclines struct{}
+
+func (AlwaysDeclines) Charge(*creditcard.Card, money.Money) error {
+ return creditcard.ErrDeclined
+}
+```
+
+<a id="naming-doubles-multiple-types"></a>
+
+#### Несколько дублей для нескольких типов (Multiple doubles for multiple types)
+
+Но теперь предположим, что `package creditcard` содержит несколько типов, для
+которых стоит создавать дубли, как показано ниже с `Service` и `StoredValue`:
+
+```go
+package creditcard
+
+type Service struct {
+ // опущено
+}
+
+type Card struct {
+ // опущено
+}
+
+// StoredValue управляет кредитными балансами клиентов. Это применяется, когда
+// возвращенный товар зачисляется на локальный счет клиента, а не обрабатывается
+// эмитентом кредитной карты. По этой причине он реализован как отдельный сервис.
+type StoredValue struct {
+ // опущено
+}
+
+func (s *StoredValue) Credit(c *Card, amount money.Money) error { /* опущено */ }
+```
+
+В этом случае более явное именование тестовых дублей имеет смысл:
+
+```go
+// Хорошо:
+type StubService struct{}
+
+func (StubService) Charge(*creditcard.Card, money.Money) error { return nil }
+
+type StubStoredValue struct{}
+
+func (StubStoredValue) Credit(*creditcard.Card, money.Money) error { return nil }
+```
+
+<a id="naming-doubles-local-variables"></a>
+
+#### Локальные переменные в тестах (Local variables in tests)
+
+Когда переменные в ваших тестах ссылаются на дубли, выберите имя, которое
+наиболее четко отличает дубль от других production-типов, исходя из контекста.
+Рассмотрим некоторый production-код, который вы хотите протестировать:
+
+```go
+package payment
+
+import (
+ "path/to/creditcard"
+ "path/to/money"
+)
+
+type CreditCard interface {
+ Charge(*creditcard.Card, money.Money) error
+}
+
+type Processor struct {
+ CC CreditCard
+}
+
+var ErrBadInstrument = errors.New("payment: instrument is invalid or expired")
+
+func (p *Processor) Process(c *creditcard.Card, amount money.Money) error {
+ if c.Expired() {
+ return ErrBadInstrument
+ }
+ return p.CC.Charge(c, amount)
+}
+```
+
+В тестах тестовой дубль типа "шпион" (spy) для `CreditCard` противопоставляется
+production-типам, поэтому добавление префикса к имени может улучшить ясность.
+
+```go
+// Хорошо:
+package payment
+
+import "path/to/creditcardtest"
+
+func TestProcessor(t *testing.T) {
+ var spyCC creditcardtest.Spy
+ proc := &Processor{CC: spyCC}
+
+ // объявления опущены: card и amount
+ if err := proc.Process(card, amount); err != nil {
+ t.Errorf("proc.Process(card, amount) = %v, want nil", err)
+ }
+
+ charges := []creditcardtest.Charge{
+ {Card: card, Amount: amount},
+ }
+
+ if got, want := spyCC.Charges, charges; !cmp.Equal(got, want) {
+ t.Errorf("spyCC.Charges = %v, want %v", got, want)
+ }
+}
+```
+
+Это понятнее, чем когда имя не имеет префикса.
+
+```go
+// Плохо:
+package payment
+
+import "path/to/creditcardtest"
+
+func TestProcessor(t *testing.T) {
+ var cc creditcardtest.Spy
+
+ proc := &Processor{CC: cc}
+
+ // объявления опущены: card и amount
+ if err := proc.Process(card, amount); err != nil {
+ t.Errorf("proc.Process(card, amount) = %v, want nil", err)
+ }
+
+ charges := []creditcardtest.Charge{
+ {Card: card, Amount: amount},
+ }
+
+ if got, want := cc.Charges, charges; !cmp.Equal(got, want) {
+ t.Errorf("cc.Charges = %v, want %v", got, want)
+ }
+}
+```
+
+<a id="shadowing"></a>
+
+### Затенение (Shadowing)
+
+**Примечание:** Это объяснение использует два неформальных термина, *stomping* и
+*shadowing*. Они не являются официальными концепциями в спецификации языка Go.
+
+Как и во многих языках программирования, в Go есть изменяемые переменные:
+присваивание переменной меняет ее значение.
+
+```go
+// Хорошо:
+func abs(i int) int {
+ if i < 0 {
+ i *= -1
+ }
+ return i
+}
+```
+
+При использовании [короткого объявления переменных (short variable
+declarations)] с оператором `:=` в некоторых случаях новая переменная не
+создается. Мы можем назвать это *stomping* (затирание). Это допустимо, когда
+исходное значение больше не нужно.
+
+```go
+// Хорошо:
+// innerHandler — это вспомогательная функция для некоторого обработчика запросов, который сам
+// выполняет запросы к другим бэкендам.
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ // Безусловно ограничиваем срок действия (deadline) для этой части обработки запроса.
+ ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+
+ // Код здесь больше не имеет доступа к оригинальному контексту.
+ // Это хороший стиль, если при первом написании вы предполагаете,
+ // что даже по мере роста кода ни одна операция законно не должна
+ // использовать (возможно, неограниченный) оригинальный контекст, предоставленный вызывающей стороной.
+
+ // ...
+}
+```
+
+Однако будьте осторожны с использованием короткого объявления переменных в новой
+области видимости: это вводит новую переменную. Мы можем назвать это *shadowing*
+(затенение) исходной переменной. Код после конца блока ссылается на оригинал.
+Вот ошибочная попытка условно сократить срок действия (deadline):
+
+```go
+// Плохо:
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ // Попытка условно ограничить срок действия.
+ if *shortenDeadlines {
+ ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+ }
+
+ // ОШИБКА: "ctx" здесь снова означает контекст, предоставленный вызывающей стороной.
+ // Вышеуказанный ошибочный код скомпилировался, потому что и ctx, и cancel
+ // использовались внутри оператора if.
+
+ // ...
+}
+```
+
+Правильная версия кода может быть такой:
+
+```go
+// Хорошо:
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ if *shortenDeadlines {
+ var cancel func()
+ // Обратите внимание на использование простого присваивания, =, а не :=.
+ ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+ }
+ // ...
+}
+```
+
+В случае, который мы назвали stomping, поскольку нет новой переменной, тип
+присваиваемого значения должен совпадать с типом исходной переменной. При
+затенении вводится совершенно новая сущность, поэтому она может иметь другой
+тип. Намеренное затенение может быть полезной практикой, но вы всегда можете
+использовать новое имя, если это улучшает [ясность (clarity)](https://neonxp.ru/pages/gostyleguide/google/guide/#clarity).
+
+Не рекомендуется использовать переменные с теми же именами, что и у стандартных
+пакетов, за исключением очень маленьких областей видимости, потому что это
+делает функции и значения из этого пакета недоступными. И наоборот, при выборе
+имени для вашего пакета избегайте имен, которые, вероятно, потребуют
+[переименования импорта (import renaming)](https://neonxp.ru/pages/gostyleguide/google/decisions/#import-renaming) или
+вызовут затенение иначе хороших имен переменных на стороне клиента.
+
+```go
+// Плохо:
+func LongFunction() {
+ url := "https://example.com/"
+ // Упс, теперь мы не можем использовать net/url в коде ниже.
+}
+```
+
+[короткого объявления переменных (short variable declarations)]:
+ https://go.dev/ref/spec#Short_variable_declarations
+
+<a id="util-packages"></a>
+
+### Пакеты `util` (Util packages)
+
+Пакеты Go имеют имя, указанное в объявлении `package`, отдельное от пути
+импорта. Имя пакета имеет большее значение для читаемости, чем путь.
+
+Имена пакетов Go должны быть [связаны с тем, что предоставляет
+пакет](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-names). Называть пакет просто `util`, `helper`,
+`common` или подобным обычно плохой выбор (хотя это может быть использовано как
+*часть* имени). Неинформативные имена затрудняют чтение кода, и если они
+используются слишком широко, они могут вызывать ненужные [конфликты
+импорта](https://neonxp.ru/pages/gostyleguide/google/decisions/#import-renaming).
+
+Вместо этого подумайте, как будет выглядеть место вызова (callsite).
+
+```go
+// Хорошо:
+db := spannertest.NewDatabaseFromFile(...)
+
+_, err := f.Seek(0, io.SeekStart)
+
+b := elliptic.Marshal(curve, x, y)
+```
+
+Вы можете примерно понять, что делает каждая из этих строк, даже не зная списка
+импортов (`cloud.google.com/go/spanner/spannertest`, `io` и `crypto/elliptic`).
+С менее сфокусированными именами они могли бы читаться так:
+
+```go
+// Плохо:
+db := test.NewDatabaseFromFile(...)
+
+_, err := f.Seek(0, common.SeekStart)
+
+b := helper.Marshal(curve, x, y)
+```
+
+<a id="package-size"></a>
+
+## Размер пакета (Package size)
+
+Если вы задаетесь вопросом, насколько большими должны быть ваши пакеты Go и
+следует ли помещать связанные типы в один пакет или разделять их на разные,
+хорошим началом будет [пост в блоге Go об именах пакетов][blog-pkg-names].
+Несмотря на название поста, он не только об именовании. Он содержит полезные
+подсказки и ссылается на несколько полезных статей и докладов.
+
+Вот некоторые другие соображения и примечания.
+
+Пользователи видят [godoc] для пакета на одной странице, и любые
+экспортированные методы типов, предоставляемых пакетом, группируются по их типу.
+Godoc также группирует конструкторы вместе с типами, которые они возвращают.
+Если *клиентскому коду* (client code) вероятно потребуется, чтобы два значения
+разных типов взаимодействовали друг с другом, может быть удобно для пользователя
+иметь их в одном пакете.
+
+Код внутри пакета имеет доступ к неэкспортированным идентификаторам пакета. Если
+у вас есть несколько связанных типов, *реализация* которых тесно связана,
+размещение их в одном пакете позволяет достичь этой связи без загрязнения
+публичного API этими деталями. Хороший тест для этой связи — представить
+гипотетического пользователя двух пакетов, где пакеты охватывают тесно связанные
+темы: если пользователь должен импортировать оба пакета, чтобы использовать
+любой из них хоть сколько-нибудь значимо, обычно правильным решением будет
+объединить их вместе. Стандартная библиотека в целом хорошо демонстрирует такую
+область видимости (scoping) и слоистость (layering).
+
+При всем сказанном, помещение всего вашего проекта в один пакет, вероятно,
+сделает этот пакет слишком большим. Когда что-то концептуально отличается,
+предоставление ему собственного небольшого пакета может облегчить его
+использование. Короткое имя пакета, известное клиентам, вместе с именем
+экспортированного типа работают вместе, чтобы создать значимый идентификатор:
+например, `bytes.Buffer`, `ring.New`. [Пост об именах пакетов][blog-pkg-names]
+содержит больше примеров.
+
+Стиль Go гибок относительно размера файлов, потому что сопровождающие могут
+перемещать код внутри пакета из одного файла в другой, не влияя на вызывающую
+сторону. Но в качестве общего руководства: обычно не стоит иметь один файл с
+тысячами строк или множество крошечных файлов. Нет такого соглашения, как "один
+тип — один файл", как в некоторых других языках. Эмпирическое правило: файлы
+должны быть достаточно сфокусированными, чтобы сопровождающий мог определить, в
+каком файле что-то находится, и достаточно маленькими, чтобы было легко найти
+это, когда вы там окажетесь. Стандартная библиотека часто разделяет большие
+пакеты на несколько исходных файлов, группируя связанный код по файлам. Исходный
+код [пакета `bytes`] является хорошим примером. Пакеты с длинной документацией
+могут выбрать выделение одного файла с именем `doc.go`, который содержит
+[документацию пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-comments), объявление пакета и больше
+ничего, но это не обязательно.
+
+Внутри кодовой базы Google и в проектах, использующих Bazel, структура каталогов
+для кода Go отличается от таковой в open source проектах на Go: вы можете иметь
+несколько целей `go_library` в одном каталоге. Хорошей причиной для выделения
+каждому пакету собственного каталога является ожидание открытия исходного кода
+вашего проекта в будущем.
+
+Несколько неканонических справочных примеров, чтобы помочь продемонстрировать
+эти идеи на практике:
+
+* маленькие пакеты, содержащие одну связную идею, которая не требует
+ добавления или удаления чего-либо еще:
+
+ * [пакет `csv`][package `csv`]: кодирование и декодирование данных CSV с
+ разделением ответственности соответственно между [reader.go] и
+ [writer.go].
+ * [пакет `expvar`][package `expvar`]: "белый ящик" (whitebox) телеметрии
+ программы, полностью содержащийся в [expvar.go].
+
+* пакеты умеренного размера, содержащие одну большую предметную область и
+ несколько связанных с ней ответственностей:
+
+ * [пакет `flag`][package `flag`]: управление флагами командной строки,
+ полностью содержащееся в [flag.go].
+
+* большие пакеты, которые разделяют несколько тесно связанных предметных
+ областей по нескольким файлам:
+
+ * [пакет `http`][package `http`]: ядро HTTP: [client.go][http-client],
+ поддержка HTTP-клиентов; [server.go][http-server], поддержка
+ HTTP-серверов; [cookie.go], управление куками.
+ * [пакет `os`][package `os`]: кроссплатформенные абстракции операционной
+ системы: [exec.go], управление подпроцессами; [file.go], управление
+ файлами; [tempfile.go], временные файлы.
+
+См. также:
+
+* [Пакеты тестовых дублей (Test double packages)](#naming-doubles)
+* [Organizing Go Code (Blog Post)]
+* [Organizing Go Code (Presentation)]
+
+[blog-pkg-names]: https://go.dev/blog/package-names
+[пакет `bytes`]: https://go.dev/src/bytes/
+[Organizing Go Code (Blog Post)]: https://go.dev/blog/organizing-go-code
+[Organizing Go Code (Presentation)]: https://go.dev/talks/2014/organizeio.slide
+[пакет `csv`]: https://pkg.go.dev/encoding/csv
+[reader.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/encoding/csv/reader.go
+[writer.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/encoding/csv/writer.go
+[пакет `expvar`]: https://pkg.go.dev/expvar
+[expvar.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/expvar/expvar.go
+[пакет `flag`]: https://pkg.go.dev/flag
+[flag.go]: https://go.googlesource.com/go/+/refs/heads/master/src/flag/flag.go
+[godoc]: https://pkg.go.dev/
+[пакет `http`]: https://pkg.go.dev/net/http
+[http-client]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/client.go
+[http-server]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/server.go
+[cookie.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/cookie.go
+[пакет `os`]: https://pkg.go.dev/os
+[exec.go]: https://go.googlesource.com/go/+/refs/heads/master/src/os/exec.go
+[file.go]: https://go.googlesource.com/go/+/refs/heads/master/src/os/file.go
+[tempfile.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/os/tempfile.go
+
+<a id="imports"></a>
+
+## Импорт (Imports)
+
+<a id="import-protos"></a>
+
+### Сообщения Protocol Buffer и заглушки (Stubs)
+
+Импорты библиотек proto обрабатываются иначе, чем стандартные импорты Go, из-за
+их межъязыковой природы. Соглашение для переименованных импортов proto основано
+на правиле, которое сгенерировало пакет:
+
+* Суффикс `pb` обычно используется для правил `go_proto_library`.
+* Суффикс `grpc` обычно используется для правил `go_grpc_library`.
+
+Часто используется одно слово, описывающее пакет:
+
+```go
+// Хорошо:
+import (
+ foopb "path/to/package/foo_service_go_proto"
+ foogrpc "path/to/package/foo_service_go_grpc"
+)
+```
+
+Следуйте рекомендациям по стилю для [имен
+пакетов](https://google.github.io/styleguide/go/decisions#package-names).
+Предпочитайте целые слова. Короткие имена хороши, но избегайте неоднозначности.
+В случае сомнений используйте имя пакета proto до _go с суффиксом pb:
+
+```go
+// Хорошо:
+import (
+ pushqueueservicepb "path/to/package/push_queue_service_go_proto"
+)
+```
+
+**Примечание:** Предыдущие рекомендации поощряли очень короткие имена, такие как
+"xpb" или даже просто "pb". Новый код должен предпочитать более описательные
+имена. Существующий код, использующий короткие имена, не должен использоваться в
+качестве примера, но его не нужно менять.
+
+<a id="import-order"></a>
+
+### Порядок импорта (Import ordering)
+
+См. [Go Style Decisions: Группировка импортов](https://neonxp.ru/pages/gostyleguide/google/decisions/.md#import-grouping).
+
+<a id="error-handling"></a>
+
+## Обработка ошибок (Error handling)
+
+В Go [ошибки — это значения (errors are values)]; они создаются кодом и
+потребляются кодом. Ошибки могут быть:
+
+* Преобразованы в диагностическую информацию для отображения человеку
+* Использованы сопровождающим
+* Интерпретированы конечным пользователем
+
+Сообщения об ошибках также появляются на самых разных поверхностях, включая
+сообщения журнала (log messages), дампы ошибок и отрисованные пользовательские
+интерфейсы.
+
+Код, который обрабатывает (производит или потребляет) ошибки, должен делать это
+осознанно. Может возникнуть соблазн проигнорировать или слепо распространить
+возвращаемое значение ошибки. Однако всегда стоит подумать, находится ли текущая
+функция в стеке вызовов в наилучшей позиции для обработки ошибки. Это обширная
+тема, и трудно дать категоричные рекомендации. Используйте свое суждение, но
+учитывайте следующие соображения:
+
+* Создавая значение ошибки, решите, придавать ли ему какую-либо
+ [структуру](#error-structure).
+* Обрабатывая ошибку, рассмотрите возможность [добавления
+ информации](#error-extra-info), которая есть у вас, но которой может не быть
+ у вызывающей и/или вызываемой стороны.
+* См. также рекомендации по [логированию ошибок](#error-logging).
+
+Хотя обычно нецелесообразно игнорировать ошибку, разумным исключением из этого
+является оркестрация связанных операций, где часто только первая ошибка полезна.
+Пакет [`errgroup`] предоставляет удобную абстракцию для группы операций, которые
+могут завершиться ошибкой или быть отменены как группа.
+
+[ошибки — это значения (errors are values)]:
+ https://go.dev/blog/errors-are-values
+[`errgroup`]: https://pkg.go.dev/golang.org/x/sync/errgroup
+
+См. также:
+
+* [Effective Go об ошибках](https://go.dev/doc/effective_go#errors)
+* [Пост в блоге Go об ошибках](https://go.dev/blog/go1.13-errors)
+* [Пакет `errors`](https://pkg.go.dev/errors)
+* [Пакет
+ `upspin.io/errors`](https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html)
+* [GoTip #89: When to Use Canonical Status Codes as
+ Errors](https://google.github.io/styleguide/go/index.html#gotip)
+* [GoTip #48: Error Sentinel
+ Values](https://google.github.io/styleguide/go/index.html#gotip)
+* [GoTip #13: Designing Errors for
+ Checking](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="error-structure"></a>
+
+### Структура ошибок (Error structure)
+
+Если вызывающим сторонам необходимо анализировать ошибку (например, различать
+различные условия ошибки), придайте значению ошибки структуру, чтобы это можно
+было сделать программно, а не заставлять вызывающую сторону выполнять
+сопоставление строк. Этот совет применим как к production-коду, так и к тестам,
+которые заботятся о разных условиях ошибок.
+
+Простейшие структурированные ошибки — это непараметризованные глобальные
+значения.
+
+```go
+type Animal string
+
+var (
+ // ErrDuplicate возникает, если это животное уже было замечено.
+ ErrDuplicate = errors.New("duplicate")
+
+ // ErrMarsupial возникает, потому что у нас аллергия на сумчатых за пределами Австралии.
+ // Извините.
+ ErrMarsupial = errors.New("marsupials are not supported")
+)
+
+func process(animal Animal) error {
+ switch {
+ case seen[animal]:
+ return ErrDuplicate
+ case marsupial(animal):
+ return ErrMarsupial
+ }
+ seen[animal] = true
+ // ...
+ return nil
+}
+```
+
+Вызывающая сторона может просто сравнить возвращенное значение ошибки функции с
+одним из известных значений ошибок:
+
+```go
+// Хорошо:
+func handlePet(...) {
+ switch err := process(an); err {
+ case ErrDuplicate:
+ return fmt.Errorf("feed %q: %v", an, err)
+ case ErrMarsupial:
+ // Попробуем восстановиться с помощью друга.
+ alternate = an.BackupAnimal()
+ return handlePet(..., alternate, ...)
+ }
+}
+```
+
+Выше используются сторожевые (sentinel) значения, где ошибка должна быть равна
+(в смысле `==`) ожидаемому значению. Во многих случаях это вполне адекватно.
+Если `process` возвращает обернутые ошибки (wrapped errors) (обсуждается ниже),
+вы можете использовать [`errors.Is`].
+
+```go
+// Хорошо:
+func handlePet(...) {
+ switch err := process(an); {
+ case errors.Is(err, ErrDuplicate):
+ return fmt.Errorf("feed %q: %v", an, err)
+ case errors.Is(err, ErrMarsupial):
+ // ...
+ }
+}
+```
+
+Не пытайтесь различать ошибки на основе их строковой формы. (См. [GoTip #13:
+Designing Errors for
+Checking](https://google.github.io/styleguide/go/index.html#gotip) для получения
+дополнительной информации.)
+
+```go
+// Плохо:
+func handlePet(...) {
+ err := process(an)
+ if regexp.MatchString(`duplicate`, err.Error()) {...}
+ if regexp.MatchString(`marsupial`, err.Error()) {...}
+}
+```
+
+Если в ошибке есть дополнительная информация, которая нужна вызывающей стороне
+программно, ее, в идеале, следует представить структурно. Например, тип
+[`os.PathError`] документирован так, что помещает путь к операции, завершившейся
+неудачей, в поле структуры, к которому вызывающая сторона может легко получить
+доступ.
+
+Могут использоваться и другие структуры ошибок, например, структура проекта,
+содержащая код ошибки и строку с деталями. [Пакет `status`][status] —
+распространенная инкапсуляция; если вы выбираете этот подход (вы не обязаны это
+делать), используйте [канонические коды (canonical codes)]. См. [GoTip #89:
+When to Use Canonical Status Codes as
+Errors](https://google.github.io/styleguide/go/index.html#gotip) чтобы понять,
+является ли использование кодов статуса правильным выбором.
+
+[`os.PathError`]: https://pkg.go.dev/os#PathError
+[`errors.Is`]: https://pkg.go.dev/errors#Is
+[`errors.As`]: https://pkg.go.dev/errors#As
+[`package cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[status]: https://pkg.go.dev/google.golang.org/grpc/status
+[канонические коды (canonical codes)]:
+ https://pkg.go.dev/google.golang.org/grpc/codes
+
+<a id="error-extra-info"></a>
+
+### Добавление информации к ошибкам (Adding information to errors)
+
+Добавляя информацию к ошибкам, избегайте избыточной информации, которую уже
+предоставляет лежащая в основе ошибка. Пакет `os`, например, уже включает
+информацию о пути в своих ошибках.
+
+```go
+// Хорошо:
+if err := os.Open("settings.txt"); err != nil {
+ return fmt.Errorf("launch codes unavailable: %v", err)
+}
+
+// Вывод:
+//
+// launch codes unavailable: open settings.txt: no such file or directory
+```
+
+Здесь "launch codes unavailable" добавляет конкретный смысл ошибке `os.Open`,
+релевантный для контекста текущей функции, без дублирования информации о пути к
+файлу.
+
+```go
+// Плохо:
+if err := os.Open("settings.txt"); err != nil {
+ return fmt.Errorf("could not open settings.txt: %v", err)
+}
+
+// Вывод:
+//
+// could not open settings.txt: open settings.txt: no such file or directory
+```
+
+Не добавляйте аннотацию, если ее единственная цель — указать на сбой без
+добавления новой информации. Наличие ошибки достаточно передает сбой вызывающей
+стороне.
+
+```go
+// Плохо:
+return fmt.Errorf("failed: %v", err) // просто верните err вместо этого
+```
+
+[Выбор между `%v` и `%w` при оборачивании ошибок (wrapping
+errors)](https://go.dev/blog/go1.13-errors#whether-to-wrap) с помощью
+`fmt.Errorf` — это тонкое решение, которое значительно влияет на то, как ошибки
+распространяются, обрабатываются, проверяются и документируются в вашем
+приложении. Основной принцип — сделать значения ошибок полезными для их
+наблюдателей, будь то люди или код.
+
+1. **`%v` для простой аннотации или новой ошибки**
+
+ Глагол `%v` — это ваш универсальный инструмент для строкового форматирования
+ любого значения Go, включая ошибки. При использовании с `fmt.Errorf` он
+ встраивает строковое представление ошибки (то, что возвращает ее метод
+ `Error()`) в новое значение ошибки, отбрасывая любую структурированную
+ информацию из исходной ошибки. Примеры использования `%v`:
+
+ * Добавление интересного, не избыточного контекста: как в примере выше.
+
+ * Логирование или отображение ошибок: Когда основная цель — представить
+ удобочитаемое сообщение об ошибке в журналах или пользователю, и вы не
+ планируете, чтобы вызывающая сторона программно проверяла ошибку с
+ помощью `errors.Is` или `errors.As` (Примечание: `errors.Unwrap` здесь,
+ как правило, не рекомендуется, так как он не обрабатывает множественные
+ ошибки (multi-errors)).
+
+ * Создание новых, независимых ошибок: Иногда необходимо преобразовать
+ ошибку в новое сообщение об ошибке, тем самым скрывая специфику исходной
+ ошибки. Эта практика особенно полезна на границах систем, включая,
+ помимо прочего, RPC, IPC и хранилища, где мы переводим
+ доменно-специфичные ошибки в каноническое пространство ошибок.
+
+ ```go
+ // Хорошо:
+ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
+ // ...
+ if err != nil {
+ return nil, fmt.Errorf("couldn't find fortune database: %v", err)
+ }
+ }
+ ```
+
+ Мы также могли бы явно аннотировать RPC код `Internal` в примере выше.
+
+ ```go
+ // Хорошо:
+ import (
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ )
+
+ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
+ // ...
+ if err != nil {
+ // Или используйте fmt.Errorf с глаголом %w, если намеренно оборачиваете ошибку,
+ // которую вызывающая сторона должна развернуть (unwrap).
+ return nil, status.Errorf(codes.Internal, "couldn't find fortune database", status.ErrInternal)
+ }
+ }
+ ```
+
+1. **`%w` (wrap) для программной проверки и цепочки ошибок (error chaining)**
+
+ Глагол `%w` специально предназначен для оборачивания ошибок (error
+ wrapping). Он создает новую ошибку, которая предоставляет метод `Unwrap()`,
+ позволяя вызывающим сторонам программно проверять цепочку ошибок с помощью
+ `errors.Is` и `errors.As`. Примеры использования `%w`:
+
+ * Добавление контекста с сохранением исходной ошибки для программной
+ проверки: Это основной случай использования во вспомогательных функциях
+ (helpers) вашего приложения. Вы хотите обогатить ошибку дополнительным
+ контекстом (например, какая операция выполнялась, когда она завершилась
+ неудачей), но при этом позволить вызывающей стороне проверить, является
+ ли лежащая в основе ошибка конкретной сторожевой ошибкой или типом.
+
+ ```go
+ // Хорошо:
+ func (s *Server) internalFunction(ctx context.Context) error {
+ // ...
+ if err != nil {
+ return fmt.Errorf("couldn't find remote file: %w", err)
+ }
+ }
+ ```
+
+ Это позволяет функции более высокого уровня выполнить `errors.Is(err,
+ fs.ErrNotExist)`, даже если исходная ошибка была обернута.
+
+ В точках, где ваша система взаимодействует с внешними системами, такими
+ как RPC, IPC или хранилище, часто лучше переводить доменно-специфичные
+ ошибки в стандартизированное пространство ошибок (например, коды статуса
+ gRPC), а не просто оборачивать исходную ошибку с помощью `%w`. Клиента
+ обычно не волнует точная внутренняя ошибка файловой системы; их волнует
+ канонический результат (например, `Internal`, `NotFound`,
+ `PermissionDenied`).
+
+ * Когда вы явно документируете и тестируете лежащие в основе ошибки,
+ которые вы раскрываете: Если API вашего пакета гарантирует, что
+ определенные лежащие в основе ошибки могут быть развернуты и проверены
+ вызывающими сторонами (например, "эта функция может вернуть
+ `ErrInvalidConfig`, обернутый в более общую ошибку"), то `%w` уместен.
+ Это становится частью контракта вашего пакета.
+
+См. также:
+
+* [Соглашения по документации ошибок (Error Documentation
+ Conventions)](#documentation-conventions-errors)
+* [Пост в блоге об оборачивании ошибок](https://blog.golang.org/go1.13-errors)
+
+<a id="error-percent-w"></a>
+
+### Размещение `%w` в ошибках (Placement of %w in errors)
+
+Предпочитайте размещать `%w` в конце строки ошибки *если* вы используете
+[оборачивание ошибок (error wrapping)](https://go.dev/blog/go1.13-errors) с
+глаголом форматирования `%w`.
+
+Ошибки могут быть обернуты с помощью глагола `%w` или путем помещения их в
+[структурированную
+ошибку](https://google.github.io/styleguide/go/index.html#gotip), которая
+реализует `Unwrap() error` (например,
+[`fs.PathError`](https://pkg.go.dev/io/fs#PathError)).
+
+Обернутые ошибки образуют цепочки ошибок (error chains): каждый новый слой
+обертывания добавляет новую запись в начало цепочки ошибок. Цепочку ошибок можно
+обойти с помощью метода `Unwrap() error`. Например:
+
+```go
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2: %w", err1)
+err3 := fmt.Errorf("err3: %w", err2)
+```
+
+Это формирует цепочку ошибок следующего вида:
+
+```mermaid
+flowchart LR
+ err3 == err3 wraps err2 ==> err2;
+ err2 == err2 wraps err1 ==> err1;
+```
+
+Независимо от того, где размещен глагол `%w`, возвращаемая ошибка всегда
+представляет начало цепочки ошибок, а `%w` — это следующий дочерний элемент.
+Аналогично, `Unwrap() error` всегда обходит цепочку ошибок от самой новой к
+самой старой ошибке.
+
+Однако размещение глагола `%w` влияет на то, печатается ли цепочка ошибок от
+самой новой к самой старой, от самой старой к самой новой или ни то, ни другое:
+
+```go
+// Хорошо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2: %w", err1)
+err3 := fmt.Errorf("err3: %w", err2)
+fmt.Println(err3) // err3: err2: err1
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается от самой новой к самой старой.
+```
+
+```go
+// Плохо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("%w: err2", err1)
+err3 := fmt.Errorf("%w: err3", err2)
+fmt.Println(err3) // err1: err2: err3
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается от самой старой к самой новой.
+```
+
+```go
+// Плохо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2-1 %w err2-2", err1)
+err3 := fmt.Errorf("err3-1 %w err3-2", err2)
+fmt.Println(err3) // err3-1 err2-1 err1 err2-2 err3-2
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается ни от самой новой к самой старой,
+// ни от самой старой к самой новой.
+```
+
+Поэтому, чтобы текст ошибки отражал структуру цепочки ошибок, предпочитайте
+размещать глагол `%w` в конце в форме `[...]: %w`.
+
+<a id="error-logging"></a>
+
+### Логирование ошибок (Logging errors)
+
+Иногда функциям необходимо сообщить внешней системе об ошибке, не передавая ее
+своим вызывающим сторонам. Логирование — очевидный выбор здесь; но будьте
+внимательны к тому, что и как вы логируете.
+
+* Как и [хорошие сообщения о неудачных тестах (good test failure messages)],
+ сообщения журнала должны четко выражать, что пошло не так, и помогать
+ сопровождающему, включая соответствующую информацию для диагностики
+ проблемы.
+
+* Избегайте дублирования. Если вы возвращаете ошибку, обычно лучше не
+ логировать ее самостоятельно, а позволить вызывающей стороне обработать ее.
+ Вызывающая сторона может выбрать логирование ошибки или, возможно,
+ ограничить частоту логирования с помощью [`rate.Sometimes`]. Другие варианты
+ включают попытку восстановления или даже [остановку программы]. В любом
+ случае, предоставление контроля вызывающей стороне помогает избежать спама в
+ журналах.
+
+ Однако обратной стороной этого подхода является то, что любое логирование
+ записывается с использованием координат строк вызывающей стороны.
+
+* Будьте осторожны с [PII]. Многие приемники журналов (log sinks) не являются
+ подходящими местами назначения для конфиденциальной информации конечных
+ пользователей.
+
+* Используйте `log.Error` скупо. Логирование уровня ERROR вызывает сброс
+ (flush) и является более дорогостоящим, чем более низкие уровни логирования.
+ Это может серьезно повлиять на производительность вашего кода. Принимая
+ решение между уровнями error и warning, учитывайте лучшую практику:
+ сообщения на уровне error должны быть actionable (то есть требовать
+ действий), а не просто "более серьезными", чем warning.
+
+* Внутри Google у нас есть системы мониторинга, которые можно настроить для
+ более эффективного оповещения, чем просто запись в файл журнала в надежде,
+ что кто-то его заметит. Это похоже, но не идентично стандартной библиотеке
+ [пакету `expvar`].
+
+[хорошие сообщения о неудачных тестах (good test failure messages)]:
+ https://google.github.io/styleguide/go/decisions#useful-test-failures
+[остановку программы]: #checks-and-panics
+[`rate.Sometimes`]: https://pkg.go.dev/golang.org/x/time/rate#Sometimes
+[PII]: https://en.wikipedia.org/wiki/Personal_data
+[пакет `expvar`]: https://pkg.go.dev/expvar
+
+<a id="vlog"></a>
+
+#### Пользовательские уровни детализации (Custom verbosity levels)
+
+Используйте детальное логирование ([`log.V`]) с пользой. Детальное логирование
+может быть полезно для разработки и трассировки. Установление соглашения об
+уровнях детализации может быть полезным. Например:
+
+* Записывайте небольшое количество дополнительной информации на `V(1)`
+* Трассируйте больше информации на `V(2)`
+* Выводите большие внутренние состояния на `V(3)`
+
+Чтобы минимизировать стоимость детального логирования, вы должны убедиться, что
+случайно не вызываете дорогие функции, даже когда `log.V` выключен. `log.V`
+предлагает два API. Более удобный из них несет риск этих случайных затрат. В
+случае сомнений используйте немного более многословный стиль.
+
+```go
+// Хорошо:
+for _, sql := range queries {
+ log.V(1).Infof("Handling %v", sql)
+ if log.V(2) {
+ log.Infof("Handling %v", sql.Explain())
+ }
+ sql.Run(...)
+}
+```
+
+```go
+// Плохо:
+// sql.Explain вызывается даже когда это сообщение не печатается.
+log.V(2).Infof("Handling %v", sql.Explain())
+```
+
+[`log.V`]: https://pkg.go.dev/github.com/golang/glog#V
+
+<a id="program-init"></a>
+
+### Инициализация программы (Program initialization)
+
+Ошибки инициализации программы (например, неправильные флаги и конфигурация)
+должны передаваться вверх в `main`, который должен вызвать `log.Exit` с ошибкой,
+объясняющей, как исправить ошибку. В этих случаях `log.Fatal` обычно не следует
+использовать, потому что трассировка стека, указывающая на проверку, вряд ли
+будет так полезна, как сгенерированное человеком, actionable сообщение.
+
+<a id="checks-and-panics"></a>
+
+### Проверки программы и паники (Program checks and panics)
+
+Как указано в [решении против паник (decision against panics)], стандартная
+обработка ошибок должна быть структурирована вокруг возвращаемых значений
+ошибок. Библиотеки должны предпочитать возвращать ошибку вызывающей стороне, а
+не завершать программу, особенно для временных ошибок.
+
+Иногда необходимо выполнить проверку согласованности (consistency check)
+инварианта и завершить программу, если он нарушен. Как правило, это делается
+только в том случае, если сбой проверки инварианта означает, что внутреннее
+состояние стало невосстановимым. Наиболее надежный способ сделать это в кодовой
+базе Google — вызвать `log.Fatal`. Использование `panic` в этих случаях
+ненадежно, потому что возможно, что отложенные (deferred) функции заблокируют
+или еще больше повредят внутреннее или внешнее состояние.
+
+Аналогично, сопротивляйтесь искушению восстановить паники (recover panics),
+чтобы избежать сбоев, так как это может привести к распространению поврежденного
+состояния. Чем дальше вы от паники, тем меньше вы знаете о состоянии программы,
+которая может удерживать блокировки или другие ресурсы. Затем программа может
+развить другие неожиданные режимы сбоев, которые могут еще больше затруднить
+диагностику проблемы. Вместо того чтобы пытаться обрабатывать неожиданные паники
+в коде, используйте инструменты мониторинга для выявления неожиданных сбоев и
+исправляйте связанные ошибки с высоким приоритетом.
+
+**Примечание:** Стандартный [`net/http` server] нарушает этот совет и
+восстанавливает паники из обработчиков запросов. Консенсус среди опытных
+инженеров Go заключается в том, что это была историческая ошибка. Если вы
+исследуете журналы серверов приложений на других языках, часто можно найти
+большие трассировки стека, которые остаются необработанными. Избегайте этой
+ловушки в своих серверах.
+
+[решении против паник (decision against panics)]:
+ https://google.github.io/styleguide/go/decisions#dont-panic
+[`net/http` server]: https://pkg.go.dev/net/http#Server
+
+<a id="when-to-panic"></a>
+
+### Когда использовать panic (When to panic)
+
+Стандартная библиотека вызывает panic при неправильном использовании API.
+Например, [`reflect`] вызывает panic во многих случаях, когда значение доступно
+таким образом, что предполагает его неправильную интерпретацию. Это аналогично
+паникам на ошибки ядра языка, такие как доступ к элементу среза вне его границ.
+Проверка кода и тесты должны обнаруживать такие ошибки, которые не ожидаются в
+production-коде. Эти паники действуют как проверки инвариантов, которые не
+зависят от библиотеки, поскольку стандартная библиотека не имеет доступа к
+[уровневому пакету `log`], который используется в кодовой базе Google.
+
+[`reflect`]: https://pkg.go.dev/reflect
+[уровневому пакету `log`]: decisions#logging
+
+Другой случай, когда паники могут быть полезны, хотя и нечасто, — это внутренняя
+деталь реализации пакета, которая всегда имеет соответствующее восстановление
+(recover) в цепочке вызовов. Парсеры и подобные глубоко вложенные, тесно
+связанные внутренние группы функций могут выиграть от такого дизайна, где
+проталкивание возвратов ошибок добавляет сложность без ценности.
+
+Ключевой атрибут этого дизайна заключается в том, что эти **паники никогда не
+должны выходить за границы пакета** и не должны быть частью API пакета. Обычно
+это достигается с помощью функции верхнего уровня с отложенным вызовом
+(deferred), которая использует `recover` для преобразования распространенной
+паники в возвращаемую ошибку на публичной границе API. Это требует, чтобы код,
+который вызывает panic и восстанавливается, отличал паники, которые код вызывает
+сам, от тех, которые он не вызывает:
+
+```go
+// Хорошо:
+type syntaxError struct {
+ msg string
+}
+
+func parseInt(in string) int {
+ n, err := strconv.Atoi(in)
+ if err != nil {
+ panic(&syntaxError{"not a valid integer"})
+ }
+}
+
+func Parse(in string) (_ *Node, err error) {
+ defer func() {
+ if p := recover(); p != nil {
+ sErr, ok := p.(*syntaxError)
+ if !ok {
+ panic(p) // Распространяем panic, поскольку он находится вне области нашего кода.
+ }
+ err = fmt.Errorf("syntax error: %v", sErr.msg)
+ }
+ }()
+ ... // Парсим входные данные, вызывая parseInt внутри для парсинга целых чисел
+}
+```
+
+> **Предупреждение:** Код, использующий этот шаблон, должен позаботиться об
+> управлении любыми ресурсами, связанными с кодом, запущенным в таких разделах,
+> управляемых defer (например, закрыть, освободить или разблокировать).
+>
+> См.: [Go Tip #81: Avoiding Resource Leaks in API Design]
+
+Паника также используется, когда компилятор не может идентифицировать
+недостижимый код, например, при использовании функции типа `log.Fatal`, которая
+не вернется:
+
+```go
+// Хорошо:
+func answer(i int) string {
+ switch i {
+ case 42:
+ return "yup"
+ case 54:
+ return "base 13, huh"
+ default:
+ log.Fatalf("Sorry, %d is not the answer.", i)
+ panic("unreachable")
+ }
+}
+```
+
+[Не вызывайте функции `log` до того, как флаги будут
+распарсены.](https://pkg.go.dev/github.com/golang/glog#pkg-overview) Если вы
+должны завершиться в функции инициализации пакета (в `init` или
+["must"-функции](https://neonxp.ru/pages/gostyleguide/google/decisions/#must-functions)), panic допустима вместо вызова
+фатального логирования.
+
+См. также:
+
+* [Handling panics](https://go.dev/ref/spec#Handling_panics) и [Run-time
+ Panics](https://go.dev/ref/spec#Run_time_panics) в спецификации языка
+* [Defer, Panic, and Recover](https://go.dev/blog/defer-panic-and-recover)
+* [On the uses and misuses of panics in
+ Go](https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/)
+
+[Go Tip #81: Avoiding Resource Leaks in API Design]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation"></a>
+
+## Документация (Documentation)
+
+<a id="documentation-conventions"></a>
+
+### Соглашения (Conventions)
+
+Этот раздел дополняет раздел [комментариев (commentary)] в документе решений.
+
+Код на Go, который документирован в знакомом стиле, легче читать и менее
+вероятно, что его будут использовать неправильно, по сравнению с тем, что плохо
+задокументировано или не задокументировано вовсе. Запускаемые [примеры
+(examples)] появляются в Godoc и Code Search и являются отличным способом
+объяснить, как использовать ваш код.
+
+[комментариев (commentary)]: decisions#commentary
+[примеры (examples)]: decisions#examples
+
+<a id="documentation-conventions-params"></a>
+
+#### Параметры и конфигурация (Parameters and configuration)
+
+Не каждый параметр должен быть перечислен в документации. Это относится к:
+
+* параметрам функций и методов
+* полям структур (struct fields)
+* API для опций (options)
+
+Документируйте подверженные ошибкам или неочевидные поля и параметры, объясняя,
+почему они интересны.
+
+В следующем фрагменте выделенный комментарий добавляет мало полезной информации
+для читателя:
+
+```go
+// Плохо:
+// Sprintf форматирует в соответствии со спецификатором формата и возвращает результирующую строку.
+//
+// format — это формат, а data — данные для интерполяции.
+func Sprintf(format string, data ...any) string
+```
+
+Однако этот фрагмент демонстрирует сценарий кода, похожий на предыдущий, где
+комментарий вместо этого говорит что-то неочевидное или существенно полезное для
+читателя:
+
+```go
+// Хорошо:
+// Sprintf форматирует в соответствии со спецификатором формата и возвращает результирующую строку.
+//
+// Предоставленные данные используются для интерполяции строки формата. Если данные не соответствуют
+// ожидаемым глаголам формата или количество данных не удовлетворяет спецификации формата,
+// функция будет встраивать предупреждения об ошибках форматирования в выходную строку, как описано
+// в разделе "Format errors" выше.
+func Sprintf(format string, data ...any) string
+```
+
+Учитывайте вашу вероятную аудиторию при выборе того, что документировать и на
+какую глубину. Сопровождающие, новички в команде, внешние пользователи и даже вы
+сами через шесть месяцев могут оценить немного другую информацию, отличную от
+той, что у вас на уме, когда вы впервые начинаете писать документацию.
+
+См. также:
+
+* [GoTip #41: Identify Function Call Parameters]
+* [GoTip #51: Patterns for Configuration]
+
+[GoTip #41: Identify Function Call Parameters]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #51: Patterns for Configuration]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation-conventions-contexts"></a>
+
+#### Контексты (Contexts)
+
+Подразумевается, что отмена (cancellation) аргумента контекста прерывает
+функцию, которой он предоставлен. Если функция может возвращать ошибку, по
+соглашению это `ctx.Err()`.
+
+Этот факт не нужно повторять:
+
+```go
+// Плохо:
+// Run выполняет рабочий цикл (run loop) воркера.
+//
+// Метод будет обрабатывать работу до отмены контекста и соответственно возвращает ошибку.
+func (Worker) Run(ctx context.Context) error
+```
+
+Поскольку это подразумевается, следующее лучше:
+
+```go
+// Хорошо:
+// Run выполняет рабочий цикл воркера.
+func (Worker) Run(ctx context.Context) error
+```
+
+Когда поведение контекста отличается или неочевидно, его следует прямо
+задокументировать, если верно любое из следующего.
+
+* Функция возвращает ошибку, отличную от `ctx.Err()`, когда контекст отменен:
+
+ ```go
+ // Хорошо:
+ // Run выполняет рабочий цикл воркера.
+ //
+ // Если контекст отменен, Run возвращает nil ошибку.
+ func (Worker) Run(ctx context.Context) error
+ ```
+
+* Функция имеет другие механизмы, которые могут ее прервать или повлиять на
+ время жизни:
+
+ ```go
+ // Хорошо:
+ // Run выполняет рабочий цикл воркера.
+ //
+ // Run обрабатывает работу до отмены контекста или вызова Stop.
+ // Отмена контекста обрабатывается асинхронно внутри: run может вернуться до того,
+ // как вся работа остановится. Метод Stop является синхронным и ожидает завершения
+ // всех операций из рабочего цикла. Используйте Stop для плавного завершения работы.
+ func (Worker) Run(ctx context.Context) error
+
+ func (Worker) Stop()
+ ```
+
+* Функция имеет особые ожидания относительно времени жизни контекста, его
+ происхождения (lineage) или прикрепленных значений (attached values):
+
+ ```go
+ // Хорошо:
+ // NewReceiver начинает получать сообщения, отправленные в указанную очередь.
+ // Контекст не должен иметь дедлайна (deadline).
+ func NewReceiver(ctx context.Context) *Receiver
+
+ // Principal возвращает человеко-читаемое имя стороны, совершившей вызов.
+ // Контекст должен иметь прикрепленное к нему значение из security.NewContext.
+ func Principal(ctx context.Context) (name string, ok bool)
+ ```
+
+ **Предупреждение:** Избегайте разработки API, которые предъявляют такие
+ требования (например, отсутствие дедлайнов у контекстов) от своих вызывающих
+ сторон. Вышеприведенное — лишь пример того, как это задокументировать, если
+ этого нельзя избежать, а не одобрение такого шаблона.
+
+<a id="documentation-conventions-concurrency"></a>
+
+#### Параллелизм (Concurrency)
+
+Пользователи Go предполагают, что концептуально доступные только для чтения
+операции безопасны для параллельного использования и не требуют дополнительной
+синхронизации.
+
+Дополнительное замечание о параллелизме можно безопасно удалить в этой Godoc:
+
+```go
+// Хорошо:
+// Len возвращает количество байт непрочитанной части буфера;
+// b.Len() == len(b.Bytes()).
+//
+// Безопасно для вызова несколькими горутинами одновременно.
+func (*Buffer) Len() int
+```
+
+Однако мутирующие операции не считаются безопасными для параллельного
+использования и требуют, чтобы пользователь учитывал синхронизацию.
+
+Аналогично, дополнительное замечание о параллелизме можно безопасно удалить
+здесь:
+
+```go
+// Хорошо:
+// Grow увеличивает емкость буфера.
+//
+// Не безопасно для вызова несколькими горутинами одновременно.
+func (*Buffer) Grow(n int)
+```
+
+Настоятельно рекомендуется документировать, если верно любое из следующего.
+
+* Непонятно, является ли операция доступной только для чтения или мутирующей:
+
+ ```go
+ // Хорошо:
+ package lrucache
+
+ // Lookup возвращает данные, связанные с ключом, из кэша.
+ //
+ // Эта операция не безопасна для параллельного использования.
+ func (*Cache) Lookup(key string) (data []byte, ok bool)
+ ```
+
+ Почему? При попадании в кэш (cache hit) при поиске ключа внутреннее
+ состояние LRU-кэша мутирует. Как это реализовано, может быть неочевидно для
+ всех читателей.
+
+* Синхронизация предоставляется API:
+
+ ```go
+ // Хорошо:
+ package fortune_go_proto
+
+ // NewFortuneTellerClient возвращает *rpc.Client для сервиса FortuneTeller.
+ // Безопасно для одновременного использования несколькими горутинами.
+ func NewFortuneTellerClient(cc *rpc.ClientConn) *FortuneTellerClient
+ ```
+
+ Почему? Stubby предоставляет синхронизацию.
+
+ **Примечание:** Если API является типом и API предоставляет синхронизацию в
+ целом, по соглашению только определение типа документирует семантику.
+
+* API потребляет пользовательские реализации типов или интерфейсов, и
+ потребитель интерфейса имеет особые требования к параллелизму:
+
+ ```go
+ // Хорошо:
+ package health
+
+ // Watcher сообщает о состоянии здоровья некоторой сущности (обычно серверной службы).
+ //
+ // Методы Watcher безопасны для одновременного использования несколькими горутинами.
+ type Watcher interface {
+ // Watch отправляет true на переданный канал, когда статус Watcher изменился.
+ Watch(changed chan<- bool) (unwatch func())
+
+ // Health возвращает nil, если за которой следят сущность здорова, или
+ // ненулевую ошибку, объясняющую, почему сущность нездорова.
+ Health() error
+ }
+ ```
+
+ Почему? Является ли API безопасным для использования несколькими горутинами
+ — это часть его контракта.
+
+<a id="documentation-conventions-cleanup"></a>
+
+#### Очистка (Cleanup)
+
+Документируйте любые явные требования к очистке, которые есть у API. В противном
+случае вызывающие стороны будут использовать API неправильно, что приведет к
+утечкам ресурсов и другим возможным ошибкам.
+
+Указывайте очистки, которые зависят от вызывающей стороны:
+
+```go
+// Хорошо:
+// NewTicker возвращает новый Ticker, содержащий канал, который будет отправлять
+// текущее время на канал после каждого тика.
+//
+// Вызовите Stop, чтобы освободить ресурсы, связанные с Ticker, когда закончите.
+func NewTicker(d Duration) *Ticker
+
+func (*Ticker) Stop()
+```
+
+Если может быть неясно, как очистить ресурсы, объясните, как:
+
+```go
+// Хорошо:
+// Get выполняет GET к указанному URL.
+//
+// Когда err равен nil, resp всегда содержит ненулевой resp.Body.
+// Вызывающая сторона должна закрыть resp.Body, когда закончит читать из него.
+//
+// resp, err := http.Get("http://example.com/")
+// if err != nil {
+// // обработать ошибку
+// }
+// defer resp.Body.Close()
+// body, err := io.ReadAll(resp.Body)
+func (c *Client) Get(url string) (resp *Response, err error)
+```
+
+См. также:
+
+* [GoTip #110: Don’t Mix Exit With Defer]
+
+[GoTip #110: Don’t Mix Exit With Defer]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation-conventions-errors"></a>
+
+#### Ошибки (Errors)
+
+Документируйте значимые сторожевые значения ошибок (error sentinel values) или
+типы ошибок, которые ваши функции возвращают вызывающим сторонам, чтобы
+вызывающие стороны могли предвидеть, какие типы условий они могут обработать в
+своем коде.
+
+```go
+// Хорошо:
+package os
+
+// Read читает до len(b) байт из File и сохраняет их в b. Он возвращает
+// количество прочитанных байт и любую встреченную ошибку.
+//
+// При достижении конца файла Read возвращает 0, io.EOF.
+func (*File) Read(b []byte) (n int, err error) {
+```
+
+Когда функция возвращает определенный тип ошибки, правильно укажите, является ли
+ошибка указателем (pointer receiver) или нет:
+
+```go
+// Хорошо:
+package os
+
+type PathError struct {
+ Op string
+ Path string
+ Err error
+}
+
+// Chdir меняет текущий рабочий каталог на указанный каталог.
+//
+// Если есть ошибка, она будет типа *PathError.
+func Chdir(dir string) error {
+```
+
+Документирование того, являются ли возвращаемые значения указателями, позволяет
+вызывающим сторонам правильно сравнивать ошибки с помощью [`errors.Is`],
+[`errors.As`] и [`package cmp`]. Это связано с тем, что не указатель
+(non-pointer value) не эквивалентен указателю (pointer value).
+
+**Примечание:** В примере `Chdir` тип возвращаемого значения записан как
+`error`, а не `*PathError`, из-за [как работают нулевые значения интерфейса (nil
+interface values)](https://go.dev/doc/faq#nil_error).
+
+Документируйте общие соглашения об ошибках в [документации
+пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-comments), когда поведение применимо к большинству
+ошибок, встречающихся в пакете:
+
+```go
+// Хорошо:
+// Пакет os предоставляет независимый от платформы интерфейс к функциям операционной системы.
+//
+// Часто доступно больше информации внутри ошибки. Например, если вызов, принимающий имя файла,
+// завершается неудачей, такой как Open или Stat, ошибка будет включать имя файла, которое
+// не удалось, когда она печатается, и будет иметь тип *PathError, который может быть распакован
+// для получения дополнительной информации.
+package os
+```
+
+Вдумчивое применение этих подходов может добавить [дополнительную информацию к
+ошибкам](#error-extra-info) без особых усилий и помочь вызывающим сторонам
+избежать добавления избыточных аннотаций.
+
+См. также:
+
+* [Go Tip #106: Error Naming
+ Conventions](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #89: When to Use Canonical Status Codes as
+ Errors](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="documentation-preview"></a>
+
+### Предварительный просмотр (Preview)
+
+Go имеет [сервер
+документации](https://pkg.go.dev/golang.org/x/pkgsite/cmd/pkgsite).
+Рекомендуется предварительно просматривать документацию, которую производит ваш
+код, как до, так и во время процесса ревью кода. Это помогает проверить, что
+[форматирование godoc] отображается правильно.
+
+[форматирование godoc]: #godoc-formatting
+
+<a id="godoc-formatting"></a>
+
+### Форматирование Godoc (Godoc formatting)
+
+[Godoc] предоставляет специальный синтаксис для [форматирования документации].
+
+* Требуется пустая строка для разделения абзацев:
+
+ ```go
+ // Хорошо:
+ // LoadConfig читает конфигурацию из указанного файла.
+ //
+ // См. some/shortlink для подробностей о формате файла конфигурации.
+ ```
+
+* Файлы тестов могут содержать [запускаемые примеры (runnable examples)],
+ которые появляются прикрепленными к соответствующей документации в godoc:
+
+ ```go
+ // Хорошо:
+ func ExampleConfig_WriteTo() {
+ cfg := &Config{
+ Name: "example",
+ }
+ if err := cfg.WriteTo(os.Stdout); err != nil {
+ log.Exitf("Failed to write config: %s", err)
+ }
+ // Output:
+ // {
+ // "name": "example"
+ // }
+ }
+ ```
+
+* Отступ строк на два дополнительных пробела форматирует их буквально
+ (verbatim):
+
+ ```go
+ // Хорошо:
+ // Update выполняет функцию в атомарной транзакции.
+ //
+ // Обычно это используется с анонимной TransactionFunc:
+ //
+ // if err := db.Update(func(state *State) { state.Foo = bar }); err != nil {
+ // //...
+ // }
+ ```
+
+ Однако обратите внимание, что часто может быть более уместно поместить код в
+ запускаемый пример, а не включать его в комментарий.
+
+ Это буквальное форматирование может быть использовано для форматирования, не
+ родного для godoc, такого как списки и таблицы:
+
+ ```go
+ // Хорошо:
+ // LoadConfig читает конфигурацию из указанного файла.
+ //
+ // LoadConfig обрабатывает следующие ключи особым образом:
+ // "import" заставит эту конфигурацию наследовать из указанного файла.
+ // "env" если присутствует, будет заполнен системным окружением.
+ ```
+
+* Одна строка, которая начинается с заглавной буквы, не содержит знаков
+ препинания, кроме скобок и запятых, и за которой следует другой абзац,
+ форматируется как заголовок:
+
+ ```go
+ // Хорошо:
+ // Следующая строка форматируется как заголовок.
+ //
+ // Использование заголовков
+ //
+ // Заголовки поставляются с автоматически сгенерированными якорными тегами для удобного связывания.
+ ```
+
+[Godoc]: https://pkg.go.dev/
+[форматирования документации]: https://go.dev/doc/comment
+[запускаемые примеры (runnable examples)]: decisions#examples
+
+<a id="signal-boost"></a>
+
+### Усиление сигнала (Signal boosting)
+
+Иногда строка кода выглядит как нечто обычное, но на самом деле это не так. Один
+из лучших примеров этого — проверка `err == nil` (поскольку `err != nil`
+встречается гораздо чаще). Следующие две условные проверки трудно различить:
+
+```go
+// Хорошо:
+if err := doSomething(); err != nil {
+ // ...
+}
+```
+
+```go
+// Плохо:
+if err := doSomething(); err == nil {
+ // ...
+}
+```
+
+Вы можете вместо этого "усилить" сигнал условного оператора, добавив
+комментарий:
+
+```go
+// Хорошо:
+if err := doSomething(); err == nil { // если ошибки НЕТ
+ // ...
+}
+```
+
+Комментарий привлекает внимание к различию в условном операторе.
+
+<a id="vardecls"></a>
+
+## Объявление переменных (Variable declarations)
+
+<a id="vardeclinitialization"></a>
+
+### Инициализация (Initialization)
+
+Для единообразия предпочитайте `:=` вместо `var` при инициализации новой
+переменной ненулевым значением.
+
+```go
+// Хорошо:
+i := 42
+```
+
+```go
+// Плохо:
+var i = 42
+```
+
+<a id="vardeclzero"></a>
+
+### Объявление переменных с нулевыми значениями (Declaring variables with zero values)
+
+Следующие объявления используют [нулевое значение (zero value)]:
+
+```go
+// Хорошо:
+var (
+ coords Point
+ magic [4]byte
+ primes []int
+)
+```
+
+[нулевое значение (zero value)]: https://golang.org/ref/spec#The_zero_value
+
+Вы должны объявлять значения, используя нулевое значение, когда хотите передать
+пустое значение, которое **готово к использованию позже**. Использование
+составных литералов (composite literals) с явной инициализацией может быть
+громоздким:
+
+```go
+// Плохо:
+var (
+ coords = Point{X: 0, Y: 0}
+ magic = [4]byte{0, 0, 0, 0}
+ primes = []int(nil)
+)
+```
+
+Распространенное применение объявления с нулевым значением — когда переменная
+используется как выход при демаршалинге (unmarshalling):
+
+```go
+// Хорошо:
+var coords Point
+if err := json.Unmarshal(data, &coords); err != nil {
+```
+
+Также допустимо использовать нулевое значение в следующей форме, когда вам нужна
+переменная типа указателя:
+
+```go
+// Хорошо:
+msg := new(pb.Bar) // или "&pb.Bar{}"
+if err := proto.Unmarshal(data, msg); err != nil {
+```
+
+Если в вашей структуре нужна блокировка (lock) или другое поле, которое [не
+должно копироваться](https://neonxp.ru/pages/gostyleguide/google/decisions/#copying), вы можете сделать его типом значения
+(value type), чтобы воспользоваться преимуществами инициализации нулевым
+значением. Это означает, что содержащий тип теперь должен передаваться по
+указателю, а не по значению. Методы этого типа должны принимать
+получатели-указатели (pointer receivers).
+
+```go
+// Хорошо:
+type Counter struct {
+ // Это поле не обязательно должно быть "*sync.Mutex". Однако
+ // пользователи теперь должны передавать *Counter объекты между собой, а не Counter.
+ mu sync.Mutex
+ data map[string]int64
+}
+
+// Обратите внимание, что это должен быть получатель-указатель, чтобы предотвратить копирование.
+func (c *Counter) IncrementBy(name string, n int64)
+```
+
+Допустимо использовать типы значений для локальных переменных составных типов
+(таких как структуры и массивы), даже если они содержат такие некопируемые поля.
+Однако, если составной тип возвращается функцией, или если все обращения к нему
+в конечном итоге требуют взятия адреса, предпочтительнее объявить переменную как
+тип указателя с самого начала. Аналогично, сообщения protobuf должны объявляться
+как типы указателей.
+
+```go
+// Хорошо:
+func NewCounter(name string) *Counter {
+ c := new(Counter) // "&Counter{}" тоже подходит.
+ registerCounter(name, c)
+ return c
+}
+
+var msg = new(pb.Bar) // или "&pb.Bar{}".
+```
+
+Это потому, что `*pb.Something` удовлетворяет [`proto.Message`], а
+`pb.Something` — нет.
+
+```go
+// Плохо:
+func NewCounter(name string) *Counter {
+ var c Counter
+ registerCounter(name, &c)
+ return &c
+}
+
+var msg = pb.Bar{}
+```
+
+[`proto.Message`]: https://pkg.go.dev/google.golang.org/protobuf/proto#Message
+
+> **Важно:** Типы map должны быть явно инициализированы перед тем, как их можно
+> будет изменять. Однако чтение из map с нулевым значением вполне допустимо.
+>
+> Для типов map и slice, если код особенно чувствителен к производительности и
+> если вы заранее знаете размеры, см. раздел [подсказки по размеру (size
+> hints)](#vardeclsize).
+
+<a id="vardeclcomposite"></a>
+
+### Составные литералы (Composite literals)
+
+Следующие объявления являются [составными литералами (composite literal)]:
+
+```go
+// Хорошо:
+var (
+ coords = Point{X: x, Y: y}
+ magic = [4]byte{'I', 'W', 'A', 'D'}
+ primes = []int{2, 3, 5, 7, 11}
+ captains = map[string]string{"Kirk": "James Tiberius", "Picard": "Jean-Luc"}
+)
+```
+
+Вы должны объявлять значение с помощью составного литерала, когда знаете
+начальные элементы или члены.
+
+В отличие от этого, использование составных литералов для объявления пустых
+значений или значений без членов может быть визуально шумным по сравнению с
+[инициализацией нулевым значением](#vardeclzero).
+
+Когда вам нужен указатель на нулевое значение, у вас есть два варианта: пустые
+составные литералы и `new`. Оба варианта допустимы, но ключевое слово `new`
+может служить напоминанием читателю, что если бы потребовалось ненулевое
+значение, составной литерал не сработал бы:
+
+```go
+// Хорошо:
+var (
+ buf = new(bytes.Buffer) // непустые Buffers инициализируются конструкторами.
+ msg = new(pb.Message) // непустые proto сообщения инициализируются билдерами или установкой полей по одному.
+)
+```
+
+[составные литералы (composite literal)]:
+ https://golang.org/ref/spec#Composite_literals
+
+<a id="vardeclsize"></a>
+
+### Подсказки по размеру (Size hints)
+
+Следующие объявления используют подсказки по размеру, чтобы предварительно
+выделить емкость:
+
+```go
+// Хорошо:
+var (
+ // Предпочтительный размер буфера для целевой файловой системы: st_blksize.
+ buf = make([]byte, 131072)
+ // Обычно обрабатывается до 8-10 элементов за запуск (16 — безопасное предположение).
+ q = make([]Node, 0, 16)
+ // Каждый шард обрабатывает shardSize (обычно 32000+) элементов.
+ seen = make(map[string]bool, shardSize)
+)
+```
+
+Подсказки по размеру и предварительное выделение — важные шаги **в сочетании с
+эмпирическим анализом кода и его интеграций**, для создания производительного и
+ресурсоэффективного кода.
+
+Большинству кода не нужны подсказки по размеру или предварительное выделение, и
+он может позволить среде выполнения увеличивать срез или карту по мере
+необходимости. Допустимо предварительно выделять память, когда окончательный
+размер известен (например, при преобразовании между map и срезом), но это не
+является требованием читаемости и может не стоить загромождения в простых
+случаях.
+
+**Предупреждение:** Предварительное выделение больше памяти, чем нужно, может
+тратить память в парке (fleet) или даже вредить производительности. В случае
+сомнений см. [GoTip #3: Benchmarking Go Code] и по умолчанию используйте
+[инициализацию нулевым значением](#vardeclzero) или [объявление составным
+литералом](#vardeclcomposite).
+
+[GoTip #3: Benchmarking Go Code]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="decl-chan"></a>
+
+### Направление каналов (Channel direction)
+
+Указывайте [направление канала (channel direction)] там, где это возможно.
+
+```go
+// Хорошо:
+// sum вычисляет сумму всех значений. Она читает из канала до тех пор,
+// пока канал не закроется.
+func sum(values <-chan int) int {
+ // ...
+}
+```
+
+Это предотвращает случайные ошибки программирования, которые возможны без
+спецификации:
+
+```go
+// Плохо:
+func sum(values chan int) (out int) {
+ for v := range values {
+ out += v
+ }
+ // values уже должен быть закрыт для достижения этого кода, что означает,
+ // что второе закрытие вызовет панику.
+ close(values)
+}
+```
+
+Когда направление указано, компилятор перехватывает простые ошибки, подобные
+этой. Это также помогает передать меру владения (ownership) типу.
+
+См. также доклад Брайана Миллса "Rethinking Classical Concurrency Patterns":
+[слайды][rethinking-concurrency-slides] [видео][rethinking-concurrency-video].
+
+[rethinking-concurrency-slides]:
+ https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view?usp=sharing
+[rethinking-concurrency-video]: https://www.youtube.com/watch?v=5zXAHh5tJqQ
+[направление канала (channel direction)]: https://go.dev/ref/spec#Channel_types
+
+<a id="funcargs"></a>
+
+## Списки аргументов функций (Function argument lists)
+
+Не позволяйте сигнатуре функции становиться слишком длинной. По мере добавления
+большего количества параметров в функцию роль отдельных параметров становится
+менее ясной, а соседние параметры одного типа становится легче спутать. Функции
+с большим количеством аргументов менее запоминаемы и их труднее читать в месте
+вызова.
+
+При проектировании API рассмотрите возможность разделения высоконастраиваемой
+функции, сигнатура которой становится сложной, на несколько более простых. Они
+могут использовать общую (неэкспортируемую) реализацию, если это необходимо.
+
+Если функции требуется много входных данных, рассмотрите возможность введения
+[структуры опций (option struct)] для некоторых аргументов или использование
+более продвинутой техники [вариативных опций (variadic options)]. Основным
+критерием выбора стратегии должно быть то, как выглядит вызов функции во всех
+ожидаемых случаях использования.
+
+Приведенные ниже рекомендации в первую очередь применяются к экспортированным
+API, к которым предъявляются более высокие стандарты, чем к неэкспортированным.
+Эти методы могут быть не нужны для вашего случая использования. Используйте свое
+суждение и балансируйте между принципами [ясности (clarity)] и [наименьшей
+механизации (least mechanism)].
+
+См. также: [Go Tip #24: Use Case-Specific
+Constructions](https://google.github.io/styleguide/go/index.html#gotip)
+
+[структуры опций (option struct)]: #option-structure
+[вариативных опций (variadic options)]: #variadic-options
+[ясности (clarity)]: guide#clarity
+[наименьшей механизации (least mechanism)]: guide#least-mechanism
+
+<a id="option-structure"></a>
+
+### Структура опций (Option structure)
+
+Структура опций (option structure) — это тип struct, который собирает некоторые
+или все аргументы функции или метода, а затем передается в качестве последнего
+аргумента функции или методу. (Структура должна быть экспортирована только если
+она используется в экспортированной функции.)
+
+Использование структуры опций имеет ряд преимуществ:
+
+* Литерал структуры включает как поля, так и значения для каждого аргумента,
+ что делает их самодокументированными и затрудняет их перестановку.
+* Несущественные или "значения по умолчанию" поля могут быть опущены.
+* Вызывающие стороны могут совместно использовать структуру опций и писать
+ вспомогательные функции для работы с ней.
+* Структуры обеспечивают более чистую документацию для каждого поля, чем
+ аргументы функций.
+* Структуры опций могут расти со временем без влияния на места вызова.
+
+Вот пример функции, которую можно улучшить:
+
+```go
+// Плохо:
+func EnableReplication(ctx context.Context, config *replicator.Config, primaryRegions, readonlyRegions []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
+ // ...
+}
+```
+
+Функция выше может быть переписана со структурой опций следующим образом:
+
+```go
+// Хорошо:
+type ReplicationOptions struct {
+ Config *replicator.Config
+ PrimaryRegions []string
+ ReadonlyRegions []string
+ ReplicateExisting bool
+ OverwritePolicies bool
+ ReplicationInterval time.Duration
+ CopyWorkers int
+ HealthWatcher health.Watcher
+}
+
+func EnableReplication(ctx context.Context, opts ReplicationOptions) {
+ // ...
+}
+```
+
+Затем функцию можно вызвать в другом пакете:
+
+```go
+// Хорошо:
+func foo(ctx context.Context) {
+ // Сложный вызов:
+ storage.EnableReplication(ctx, storage.ReplicationOptions{
+ Config: config,
+ PrimaryRegions: []string{"us-east1", "us-central2", "us-west3"},
+ ReadonlyRegions: []string{"us-east5", "us-central6"},
+ OverwritePolicies: true,
+ ReplicationInterval: 1 * time.Hour,
+ CopyWorkers: 100,
+ HealthWatcher: watcher,
+ })
+
+ // Простой вызов:
+ storage.EnableReplication(ctx, storage.ReplicationOptions{
+ Config: config,
+ PrimaryRegions: []string{"us-east1", "us-central2", "us-west3"},
+ })
+}
+```
+
+**Примечание:** [Контексты никогда не включаются в структуры
+опций](https://neonxp.ru/pages/gostyleguide/google/decisions/#contexts).
+
+Этот вариант часто предпочтителен, когда применимо одно из следующих условий:
+
+* Все вызывающие стороны должны указать одну или несколько опций.
+* Большому количеству вызывающих сторон необходимо предоставить множество
+ опций.
+* Опции используются совместно несколькими функциями, которые будет вызывать
+ пользователь.
+
+<a id="variadic-options"></a>
+
+### Вариативные опции (Variadic options)
+
+Используя вариативные опции, создаются экспортированные функции, которые
+возвращают замыкания (closures), которые могут быть переданы в [вариативный
+(`...`) параметр] функции. Функция принимает в качестве параметров значения
+опции (если есть), а возвращаемое замыкание принимает изменяемую ссылку (обычно
+указатель на тип struct), которая будет обновлена на основе входных данных.
+
+[вариативный (`...`) параметр]:
+ https://golang.org/ref/spec#Passing_arguments_to_..._parameters
+
+Использование вариативных опций может предоставить ряд преимуществ:
+
+* Опции не занимают места в месте вызова, когда конфигурация не нужна.
+* Опции все еще являются значениями, поэтому вызывающие стороны могут делиться
+ ими, писать вспомогательные функции и накапливать их.
+* Опции могут принимать несколько параметров (например,
+ `cartesian.Translate(dx, dy int) TransformOption`).
+* Функции опций могут возвращать именованный тип, чтобы группировать опции
+ вместе в godoc.
+* Пакеты могут разрешать (или запрещать) сторонним пакетам определять (или
+ запрещать определение) свои собственные опции.
+
+**Примечание:** Использование вариативных опций требует значительного количества
+дополнительного кода (см. следующий пример), поэтому их следует использовать
+только тогда, когда преимущества перевешивают накладные расходы.
+
+Вот пример функции, которую можно улучшить:
+
+```go
+// Плохо:
+func EnableReplication(ctx context.Context, config *placer.Config, primaryCells, readonlyCells []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
+ ...
+}
+```
+
+Пример выше может быть переписан с вариативными опциями следующим образом:
+
+```go
+// Хорошо:
+type replicationOptions struct {
+ readonlyCells []string
+ replicateExisting bool
+ overwritePolicies bool
+ replicationInterval time.Duration
+ copyWorkers int
+ healthWatcher health.Watcher
+}
+
+// ReplicationOption настраивает EnableReplication.
+type ReplicationOption func(*replicationOptions)
+
+// ReadonlyCells добавляет дополнительные ячейки, которые дополнительно
+// должны содержать реплики только для чтения данных.
+//
+// Передача этой опции несколько раз добавит дополнительные
+// ячейки только для чтения.
+//
+// По умолчанию: нет
+func ReadonlyCells(cells ...string) ReplicationOption {
+ return func(opts *replicationOptions) {
+ opts.readonlyCells = append(opts.readonlyCells, cells...)
+ }
+}
+
+// ReplicateExisting контролирует, будут ли файлы, уже существующие в
+// первичных ячейках, реплицированы. В противном случае только недавно добавленные
+// файлы будут кандидатами на репликацию.
+//
+// Повторная передача этой опции перезапишет предыдущие значения.
+//
+// По умолчанию: false
+func ReplicateExisting(enabled bool) ReplicationOption {
+ return func(opts *replicationOptions) {
+ opts.replicateExisting = enabled
+ }
+}
+
+// ... другие опции ...
+
+// DefaultReplicationOptions управляют значениями по умолчанию перед
+// применением опций, переданных в EnableReplication.
+var DefaultReplicationOptions = []ReplicationOption{
+ OverwritePolicies(true),
+ ReplicationInterval(12 * time.Hour),
+ CopyWorkers(10),
+}
+
+func EnableReplication(ctx context.Context, config *placer.Config, primaryCells []string, opts ...ReplicationOption) {
+ var options replicationOptions
+ for _, opt := range DefaultReplicationOptions {
+ opt(&options)
+ }
+ for _, opt := range opts {
+ opt(&options)
+ }
+}
+```
+
+Затем функцию можно вызвать в другом пакете:
+
+```go
+// Хорошо:
+func foo(ctx context.Context) {
+ // Сложный вызов:
+ storage.EnableReplication(ctx, config, []string{"po", "is", "ea"},
+ storage.ReadonlyCells("ix", "gg"),
+ storage.OverwritePolicies(true),
+ storage.ReplicationInterval(1*time.Hour),
+ storage.CopyWorkers(100),
+ storage.HealthWatcher(watcher),
+ )
+
+ // Простой вызов:
+ storage.EnableReplication(ctx, config, []string{"po", "is", "ea"})
+}
+```
+
+Предпочитайте этот вариант, когда применимо большинство из следующего:
+
+* Большинству вызывающих сторон не нужно указывать никакие опции.
+* Большинство опций используется редко.
+* Существует большое количество опций.
+* Опции требуют аргументов.
+* Опции могут завершиться неудачей или быть установлены неправильно (в этом
+ случае функция опции возвращает `error`).
+* Опции требуют большого количества документации, которую трудно уместить в
+ структуре.
+* Пользователи или другие пакеты могут предоставлять пользовательские опции.
+
+Опции в этом стиле должны принимать параметры, а не использовать наличие
+(presence) для сигнализации своего значения; последнее может значительно
+усложнить динамическое составление аргументов. Например, двоичные настройки
+должны принимать логическое значение (например, `rpc.FailFast(enable bool)`
+предпочтительнее, чем `rpc.EnableFailFast()`). Перечисляемая опция должна
+принимать перечисляемую константу (например, `log.Format(log.Capacitor)`
+предпочтительнее, чем `log.CapacitorFormat()`). Альтернатива значительно
+усложняет жизнь пользователям, которые должны программно выбирать, какие опции
+передавать; такие пользователи вынуждены изменять фактический состав параметров,
+а не просто изменять аргументы опций. Не предполагайте, что все пользователи
+будут статически знать полный набор опций.
+
+Как правило, опции должны обрабатываться по порядку. Если возникает конфликт или
+если некумулятивная опция передается несколько раз, должен побеждать последний
+аргумент.
+
+Параметр функции опции в этом шаблоне обычно не экспортируется, чтобы ограничить
+определение опций только самим пакетом. Это хороший вариант по умолчанию, хотя
+могут быть случаи, когда уместно позволить другим пакетам определять опции.
+
+См. [оригинальный пост в блоге Роба Пайка] и [доклад Дейва Ченея] для более
+глубокого изучения того, как эти опции могут быть использованы.
+
+[оригинальный пост в блоге Роба Пайка]:
+ http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
+[доклад Дейва Ченея]:
+ https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
+
+<a id="complex-clis"></a>
+
+## Сложные интерфейсы командной строки (Complex command-line interfaces)
+
+Некоторые программы хотят предоставить пользователям богатый интерфейс командной
+строки, включающий подкоманды. Например, `kubectl create`, `kubectl run` и
+многие другие подкоманды предоставляются программой `kubectl`. Существует по
+крайней мере следующие общеупотребительные библиотеки для достижения этого.
+
+Если у вас нет предпочтений или другие соображения равны, рекомендуется
+[subcommands], поскольку она самая простая и с ней легко работать правильно.
+Однако, если вам нужны другие функции, которые она не предоставляет, выберите
+один из других вариантов.
+
+* **[cobra]**
+
+ * Соглашение о флагах: getopt
+ * Распространена за пределами кодовой базы Google.
+ * Много дополнительных функций.
+ * Подводные камни в использовании (см. ниже).
+
+* **[subcommands]**
+
+ * Соглашение о флагах: Go
+ * Проста и с ней легко работать правильно.
+ * Рекомендуется, если вам не нужны дополнительные функции.
+
+**Предупреждение**: функции команд cobra должны использовать `cmd.Context()` для
+получения контекста, а не создавать свой собственный корневой контекст с помощью
+`context.Background`. Код, использующий пакет subcommands, уже получает
+правильный контекст как параметр функции.
+
+Вы не обязаны помещать каждую подкоманду в отдельный пакет, и часто в этом нет
+необходимости. Применяйте те же соображения о границах пакетов, что и в любой
+кодовой базе Go. Если ваш код может использоваться как библиотека и как бинарный
+файл, обычно полезно отделить CLI-код от библиотеки, делая CLI просто еще одним
+из ее клиентов. (Это не специфично для CLI с подкомандами, но упоминается здесь,
+потому что это частое место, где это возникает.)
+
+[subcommands]: https://pkg.go.dev/github.com/google/subcommands
+[cobra]: https://pkg.go.dev/github.com/spf13/cobra
+
+<a id="tests"></a>
+
+## Тесты (Tests)
+
+<a id="test-functions"></a>
+
+### Оставляйте тестирование функции `Test`
+
+<!-- Примечание для сопровождающих: Этот раздел пересекается с decisions#assert и
+decisions#mark-test-helpers. Цель не в том, чтобы повторять информацию, а
+в том, чтобы иметь одно место, которое суммирует различие, о котором часто
+задумываются новички в языке. -->
+
+Go различает "тестовые помощники (test helpers)" и "помощники утверждений
+(assertion helpers)":
+
+* **Тестовые помощники** — это функции, которые выполняют задачи настройки или
+ очистки. Все сбои, которые происходят в тестовых помощниках, ожидаемо
+ являются сбоями окружения (а не тестируемого кода) — например, когда
+ тестовая база данных не может быть запущена, потому что на этой машине
+ больше нет свободных портов. Для таких функций часто уместно вызывать
+ `t.Helper`, чтобы [пометить их как тестовый помощник]. См. [обработку ошибок
+ в тестовых помощниках] для более подробной информации.
+
+* **Помощники утверждений** — это функции, которые проверяют правильность
+ системы и завершают тест с ошибкой, если ожидание не выполняется. Помощники
+ утверждений [не считаются идиоматичными] в Go.
+
+Цель теста — сообщить о условиях прохождения/непрохождения тестируемого кода.
+Идеальное место для завершения теста с ошибкой — внутри самой функции `Test`,
+так как это обеспечивает ясность [сообщений об ошибках] и логики теста.
+
+[пометить их как тестовый помощник]: decisions#mark-test-helpers
+[обработку ошибок в тестовых помощниках]: #test-helper-error-handling
+[не считаются идиоматичными]: decisions#assert
+[сообщений об ошибках]: decisions#useful-test-failures
+
+По мере роста вашего тестового кода может стать необходимым вынести некоторую
+функциональность в отдельные функции. Стандартные соображения программной
+инженерии все еще применяются, поскольку *тестовый код — это все еще код*. Если
+функциональность не взаимодействует с тестовым фреймворком, то применяются все
+обычные правила. Однако, когда общий код взаимодействует с фреймворком,
+необходимо соблюдать осторожность, чтобы избежать распространенных подводных
+камней, которые могут привести к неинформативным сообщениям об ошибках и
+неудобным в поддержке тестам.
+
+Если многим отдельным тестовым случаям требуется одна и та же логика валидации,
+организуйте тест одним из следующих способов вместо использования помощников
+утверждений или сложных функций валидации:
+
+* Встройте логику (и валидацию, и завершение с ошибкой) в функцию `Test`, даже
+ если это повторяется. Это лучше всего работает в простых случаях.
+* Если входные данные похожи, рассмотрите возможность объединения их в
+ [табличный тест (table-driven test)], сохраняя логику встроенной в цикл. Это
+ помогает избежать повторения, сохраняя валидацию и завершение с ошибкой в
+ `Test`.
+* Если есть несколько вызывающих сторон, которым нужна одна и та же функция
+ валидации, но табличные тесты не подходят (обычно потому, что входные данные
+ недостаточно просты или валидация требуется как часть последовательности
+ операций), организуйте функцию валидации так, чтобы она возвращала значение
+ (обычно `error`), а не принимала параметр `testing.T` и использовала его для
+ завершения теста с ошибкой. Используйте логику внутри `Test`, чтобы решить,
+ завершать ли тест с ошибкой, и предоставить [полезные сообщения об ошибках
+ теста]. Вы также можете создать тестовые помощники для выноса общего
+ шаблонного кода настройки.
+
+Дизайн, описанный в последнем пункте, сохраняет ортогональность. Например,
+[пакет `cmp`] не предназначен для завершения тестов с ошибкой, а для сравнения
+(и вычисления различий) значений. Поэтому ему не нужно знать о контексте, в
+котором было сделано сравнение, поскольку вызывающая сторона может предоставить
+его. Если ваш общий тестовый код предоставляет `cmp.Transformer` для вашего типа
+данных, это часто может быть самым простым дизайном. Для других проверок
+рассмотрите возможность возврата значения `error`.
+
+```go
+// Хорошо:
+// polygonCmp возвращает cmp.Option, которое приравнивает объекты геометрии s2
+// с некоторой небольшой ошибкой с плавающей точкой.
+func polygonCmp() cmp.Option {
+ return cmp.Options{
+ cmp.Transformer("polygon", func(p *s2.Polygon) []*s2.Loop { return p.Loops() }),
+ cmp.Transformer("loop", func(l *s2.Loop) []s2.Point { return l.Vertices() }),
+ cmpopts.EquateApprox(0.00000001, 0),
+ cmpopts.EquateEmpty(),
+ }
+}
+
+func TestFenceposts(t *testing.T) {
+ // Это тест для вымышленной функции Fenceposts, которая рисует забор
+ // вокруг некоторого объекта Place. Детали не важны, за исключением того,
+ // что результат — это некоторый объект, имеющий геометрию s2 (github.com/golang/geo/s2)
+ got := Fencepost(tomsDiner, 1*meter)
+ if diff := cmp.Diff(want, got, polygonCmp()); diff != "" {
+ t.Errorf("Fencepost(tomsDiner, 1m) returned unexpected diff (-want+got):\n%v", diff)
+ }
+}
+
+func FuzzFencepost(f *testing.F) {
+ // Фаззинг-тест (https://go.dev/doc/fuzz) для того же.
+
+ f.Add(tomsDiner, 1*meter)
+ f.Add(school, 3*meter)
+
+ f.Fuzz(func(t *testing.T, geo Place, padding Length) {
+ got := Fencepost(geo, padding)
+ // Простая эталонная реализация: не используется в prod, но проста для
+ // понимания и поэтому полезна для проверки в случайных тестах.
+ reference := slowFencepost(geo, padding)
+
+ // Во фаззинг-тесте входные и выходные данные могут быть большими, поэтому
+ // не беспокойтесь о печати diff. cmp.Equal достаточно.
+ if !cmp.Equal(got, reference, polygonCmp()) {
+ t.Errorf("Fencepost returned wrong placement")
+ }
+ })
+}
+```
+
+Функция `polygonCmp` агностична относительно того, как ее вызывают; она не
+принимает конкретный тип входных данных и не контролирует, что делать, если два
+объекта не совпадают. Поэтому больше вызывающих сторон могут использовать ее.
+
+**Примечание:** Существует аналогия между тестовыми помощниками и обычным
+библиотечным кодом. Код в библиотеках обычно [не должен вызывать panic] за
+редкими исключениями; код, вызываемый из теста, не должен останавливать тест,
+если нет [смысла продолжать].
+
+[табличный тест (table-driven test)]: decisions#table-driven-tests
+[полезные сообщения об ошибках теста]: decisions#useful-test-failures
+[пакет `cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[не должен вызывать panic]: decisions#dont-panic
+[смысла продолжать]: #t-fatal
+
+<a id="test-validation-apis"></a>
+
+### Проектирование расширяемых API валидации (Designing extensible validation APIs)
+
+Большая часть советов о тестировании в руководстве по стилю касается
+тестирования вашего собственного кода. Этот раздел о том, как предоставить
+средства для других людей тестировать код, который они пишут, чтобы убедиться,
+что он соответствует требованиям вашей библиотеки.
+
+<a id="test-validation-apis-what"></a>
+
+#### Приемочное тестирование (Acceptance testing)
+
+Такое тестирование называется [приемочным тестированием (acceptance testing)].
+Предпосылка такого тестирования заключается в том, что человек, использующий
+тест, не знает всех деталей того, что происходит в тесте; он просто передает
+входные данные в тестовое средство, чтобы оно выполнило работу. Это можно
+рассматривать как форму [инверсии управления (inversion of control)].
+
+В типичном тесте Go тестовая функция контролирует поток программы, и
+рекомендации [без утверждений (no assert)](https://neonxp.ru/pages/gostyleguide/google/decisions/#assert) и [тестовые
+функции](#test-functions) побуждают вас сохранять это так. Этот раздел
+объясняет, как создавать поддержку для таких тестов способом, согласующимся со
+стилем Go.
+
+Прежде чем углубляться в "как", рассмотрим пример из [`io/fs`], приведенный
+ниже:
+
+```go
+type FS interface {
+ Open(name string) (File, error)
+}
+```
+
+Хотя существуют хорошо известные реализации `fs.FS`, от разработчика Go может
+потребоваться создать свою. Чтобы помочь проверить правильность пользовательской
+реализации `fs.FS`, была предоставлена универсальная библиотека в
+[`testing/fstest`] под названием [`fstest.TestFS`]. Этот API рассматривает
+реализацию как черный ящик (blackbox), чтобы убедиться, что она соблюдает самые
+основные части контракта `io/fs`.
+
+[приемочным тестированием (acceptance testing)]:
+ https://en.wikipedia.org/wiki/Acceptance_testing
+[инверсии управления (inversion of control)]:
+ https://en.wikipedia.org/wiki/Inversion_of_control
+[`io/fs`]: https://pkg.go.dev/io/fs
+[`testing/fstest`]: https://pkg.go.dev/testing/fstest
+[`fstest.TestFS`]: https://pkg.go.dev/testing/fstest#TestFS
+
+<a id="test-validation-apis-writing"></a>
+
+#### Написание приемочного теста (Writing an acceptance test)
+
+Теперь, когда мы знаем, что такое приемочный тест и почему вы можете его
+использовать, давайте рассмотрим создание приемочного теста для `package chess`,
+пакета, используемого для симуляции шахматных игр. Пользователи `chess` должны
+реализовать интерфейс `chess.Player`. Эти реализации — основное, что мы будем
+проверять. Наш приемочный тест касается того, делает ли реализация игрока
+легальные ходы, а не того, являются ли ходы умными.
+
+1. Создайте новый пакет для поведения валидации, [обычно
+ именуемый](#naming-doubles-helper-package) добавлением слова `test` к имени
+ пакета (например, `chesstest`).
+
+1. Создайте функцию, которая выполняет валидацию, принимая тестируемую
+ реализацию в качестве аргумента и проверяя ее:
+
+ ```go
+ // ExercisePlayer тестирует реализацию Player за один ход на доске.
+ // Сама доска выборочно проверяется на разумность и правильность.
+ //
+ // Возвращает nil ошибку, если игрок делает правильный ход в контексте
+ // предоставленной доски. В противном случае ExercisePlayer возвращает одну из
+ // ошибок этого пакета, чтобы указать, как и почему игрок не прошел валидацию.
+ func ExercisePlayer(b *chess.Board, p chess.Player) error
+ ```
+
+ Тест должен отмечать, какие инварианты нарушены и как. Ваш дизайн может
+ выбрать одну из двух дисциплин для сообщения о сбоях:
+
+ * **Завершение при первой ошибке (Fail fast)**: возвращать ошибку, как
+ только реализация нарушает инвариант.
+
+ Это самый простой подход, и он хорошо работает, если ожидается, что
+ приемочный тест будет выполняться быстро. Простые [сторожевые ошибки
+ (sentinels)] и [пользовательские типы] могут быть легко использованы
+ здесь, что, в свою очередь, облегчает тестирование самого приемочного
+ теста.
+
+ ```go
+ for color, army := range b.Armies {
+ // Король никогда не должен покидать доску, потому что игра заканчивается
+ // матом.
+ if army.King == nil {
+ return &MissingPieceError{Color: color, Piece: chess.King}
+ }
+ }
+ ```
+
+ * **Агрегация всех сбоев (Aggregate all failures)**: собирать все сбои и
+ сообщать о них всех.
+
+ Этот подход напоминает рекомендацию [продолжать выполнение (keep going)]
+ и может быть предпочтительнее, если ожидается, что приемочный тест будет
+ выполняться медленно.
+
+ То, как вы агрегируете сбои, должно определяться тем, хотите ли вы дать
+ пользователям или себе возможность исследовать отдельные сбои (например,
+ для тестирования вашего приемочного теста). Ниже демонстрируется
+ использование [пользовательского типа ошибки][пользовательские типы],
+ который [агрегирует ошибки]:
+
+ ```go
+ var badMoves []error
+
+ move := p.Move()
+ if putsOwnKingIntoCheck(b, move) {
+ badMoves = append(badMoves, PutsSelfIntoCheckError{Move: move})
+ }
+
+ if len(badMoves) > 0 {
+ return SimulationError{BadMoves: badMoves}
+ }
+ return nil
+ ```
+
+Приемочный тест должен соблюдать рекомендацию [продолжать выполнение (keep
+going)], не вызывая `t.Fatal`, если тест не обнаруживает нарушение инварианта в
+системе, которая тестируется.
+
+Например, `t.Fatal` должен быть зарезервирован для исключительных случаев, таких
+как [сбой настройки](#test-helper-error-handling), как обычно:
+
+```go
+func ExerciseGame(t *testing.T, cfg *Config, p chess.Player) error {
+ t.Helper()
+
+ if cfg.Simulation == Modem {
+ conn, err := modempool.Allocate()
+ if err != nil {
+ t.Fatalf("No modem for the opponent could be provisioned: %v", err)
+ }
+ t.Cleanup(func() { modempool.Return(conn) })
+ }
+ // Запустить приемочный тест (целую игру).
+}
+```
+
+Эта техника может помочь вам создавать лаконичные, каноничные проверки. Но не
+пытайтесь использовать ее, чтобы обойти [рекомендации об
+утверждениях](https://neonxp.ru/pages/gostyleguide/google/decisions/#assert).
+
+Конечный продукт должен быть похож на этот для конечных пользователей:
+
+```go
+// Хорошо:
+package deepblue_test
+
+import (
+ "chesstest"
+ "deepblue"
+)
+
+func TestAcceptance(t *testing.T) {
+ player := deepblue.New()
+ err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
+ if err != nil {
+ t.Errorf("Deep Blue player failed acceptance test: %v", err)
+ }
+}
+```
+
+[сторожевые ошибки (sentinels)]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[пользовательские типы]: https://google.github.io/styleguide/go/index.html#gotip
+[агрегирует ошибки]: https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="use-real-transports"></a>
+
+### Используйте реальные транспорты (Use real transports)
+
+При тестировании интеграции компонентов, особенно когда HTTP или RPC
+используются в качестве базового транспорта между компонентами, предпочитайте
+использовать реальный базовый транспорт для подключения к тестовой версии
+бэкенда.
+
+Например, предположим, что код, который вы хотите протестировать (иногда
+называемый "системой под тестом" или SUT), взаимодействует с бэкендом,
+реализующим API [долго выполняющихся операций (long running operations)]. Чтобы
+протестировать ваш SUT, используйте реальный [OperationsClient], подключенный к
+[тестовому двойнику (test
+double)](https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts)
+(например, моку, заглушке или фейку) [OperationsServer].
+
+[тестовому двойнику (test double)]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+[долго выполняющихся операций (long running operations)]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning
+[OperationsClient]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsClient
+[OperationsServer]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsServer
+
+Это рекомендуется вместо ручной реализации клиента из-за сложности правильной
+имитации поведения клиента. Используя production-клиент с тестовым сервером, вы
+гарантируете, что ваш тест использует как можно больше реального кода.
+
+**Совет:** По возможности используйте тестовую библиотеку, предоставленную
+авторами тестируемого сервиса.
+
+<a id="t-fatal"></a>
+
+### `t.Error` против `t.Fatal`
+
+Как обсуждалось в [решениях](https://neonxp.ru/pages/gostyleguide/google/decisions/#keep-going), тесты, как правило, не
+должны прерываться при первой встреченной проблеме.
+
+Однако некоторые ситуации требуют, чтобы тест не продолжался. Вызов `t.Fatal`
+уместен, когда какая-то часть настройки теста завершается неудачей, особенно во
+[вспомогательных функциях настройки теста], без которых вы не можете запустить
+остальную часть теста. В табличном тесте `t.Fatal` уместен для сбоев, которые
+настраивают всю тестовую функцию до начала цикла теста. Сбои, которые
+затрагивают одну запись в таблице теста и делают невозможным продолжение работы
+с этой записью, должны сообщаться следующим образом:
+
+* Если вы не используете подтесты `t.Run`, используйте `t.Error`, за которым
+ следует оператор `continue` для перехода к следующей записи таблицы.
+* Если вы используете подтесты (и вы внутри вызова `t.Run`), используйте
+ `t.Fatal`, который завершает текущий подтест и позволяет вашему тестовому
+ случаю перейти к следующему подтесту.
+
+**Предупреждение:** Не всегда безопасно вызывать `t.Fatal` и подобные функции.
+[Подробнее здесь](#t-fatal-goroutine).
+
+[вспомогательных функциях настройки теста]: #test-helper-error-handling
+
+<a id="test-helper-error-handling"></a>
+
+### Обработка ошибок во вспомогательных тестовых функциях (Error handling in test helpers)
+
+**Примечание:** В этом разделе обсуждаются [тестовые помощники (test helpers)] в
+том смысле, в котором Go использует этот термин: функции, которые выполняют
+настройку и очистку теста, а не общие средства утверждений. См. раздел [тестовые
+функции](#test-functions) для более подробного обсуждения.
+
+[тестовые помощники (test helpers)]: decisions#mark-test-helpers
+
+Операции, выполняемые тестовым помощником, иногда завершаются неудачей.
+Например, настройка каталога с файлами включает ввод-вывод, который может
+завершиться неудачей. Когда тестовые помощники завершаются неудачей, их сбой
+часто означает, что тест не может продолжиться, поскольку не выполнилось
+предварительное условие настройки. Когда это происходит, предпочтительнее
+вызвать одну из функций `Fatal` в помощнике:
+
+```go
+// Хорошо:
+func mustAddGameAssets(t *testing.T, dir string) {
+ t.Helper()
+ if err := os.WriteFile(path.Join(dir, "pak0.pak"), pak0, 0644); err != nil {
+ t.Fatalf("Setup failed: could not write pak0 asset: %v", err)
+ }
+ if err := os.WriteFile(path.Join(dir, "pak1.pak"), pak1, 0644); err != nil {
+ t.Fatalf("Setup failed: could not write pak1 asset: %v", err)
+ }
+}
+```
+
+Это делает вызывающую сторону чище, чем если бы помощник возвращал ошибку самому
+тесту:
+
+```go
+// Плохо:
+func addGameAssets(t *testing.T, dir string) error {
+ t.Helper()
+ if err := os.WriteFile(path.Join(d, "pak0.pak"), pak0, 0644); err != nil {
+ return err
+ }
+ if err := os.WriteFile(path.Join(d, "pak1.pak"), pak1, 0644); err != nil {
+ return err
+ }
+ return nil
+}
+```
+
+**Предупреждение:** Не всегда безопасно вызывать `t.Fatal` и подобные функции.
+[Подробнее](#t-fatal-goroutine) здесь.
+
+Сообщение об ошибке должно включать описание того, что произошло. Это важно, так
+как вы можете предоставлять тестовый API многим пользователям, особенно с
+увеличением количества шагов, производящих ошибки, в помощнике. Когда тест
+завершается неудачей, пользователь должен знать, где и почему.
+
+**Совет:** Go 1.14 представила функцию [`t.Cleanup`], которую можно использовать
+для регистрации функций очистки, которые запускаются при завершении вашего
+теста. Функция также работает с тестовыми помощниками. См. [GoTip #4: Cleaning
+Up Your Tests](https://google.github.io/styleguide/go/index.html#gotip) для
+рекомендаций по упрощению тестовых помощников.
+
+Сниппет ниже в вымышленном файле `paint_test.go` демонстрирует, как
+`(*testing.T).Helper` влияет на сообщение об ошибке в тесте Go:
+
+```go
+package paint_test
+
+import (
+ "fmt"
+ "testing"
+)
+
+func paint(color string) error {
+ return fmt.Errorf("no %q paint today", color)
+}
+
+func badSetup(t *testing.T) {
+ // Здесь должен быть вызов t.Helper, но его нет.
+ if err := paint("taupe"); err != nil {
+ t.Fatalf("Could not paint the house under test: %v", err) // строка 15
+ }
+}
+
+func goodSetup(t *testing.T) {
+ t.Helper()
+ if err := paint("lilac"); err != nil {
+ t.Fatalf("Could not paint the house under test: %v", err)
+ }
+}
+
+func TestBad(t *testing.T) {
+ badSetup(t)
+ // ...
+}
+
+func TestGood(t *testing.T) {
+ goodSetup(t) // строка 32
+ // ...
+}
+```
+
+Вот пример вывода при запуске. Обратите внимание на выделенный текст и на то,
+как он отличается:
+
+```text
+=== RUN TestBad
+ paint_test.go:15: Could not paint the house under test: no "taupe" paint today
+--- FAIL: TestBad (0.00s)
+=== RUN TestGood
+ paint_test.go:32: Could not paint the house under test: no "lilac" paint today
+--- FAIL: TestGood (0.00s)
+FAIL
+```
+
+Ошибка с `paint_test.go:15` относится к строке функции настройки, которая
+завершилась неудачей в `badSetup`:
+
+`t.Fatalf("Could not paint the house under test: %v", err)`
+
+Тогда как `paint_test.go:32` относится к строке теста, которая завершилась
+неудачей в `TestGood`:
+
+`goodSetup(t)`
+
+Правильное использование `(*testing.T).Helper` гораздо лучше определяет
+местоположение сбоя, когда:
+
+* вспомогательные функции растут
+* вспомогательные функции вызывают другие вспомогательные функции
+* количество использований вспомогательных функций в тестовых функциях растет
+
+**Совет:** Если вспомогательная функция вызывает `(*testing.T).Error` или
+`(*testing.T).Fatal`, предоставьте некоторый контекст в строке формата, чтобы
+помочь определить, что пошло не так и почему.
+
+**Совет:** Если ничто из того, что делает помощник, не может привести к неудаче
+теста, ему не нужно вызывать `t.Helper`. Упростите его сигнатуру, удалив `t` из
+списка параметров функции.
+
+[`t.Cleanup`]: https://pkg.go.dev/testing#T.Cleanup
+
+<a id="t-fatal-goroutine"></a>
+
+### Не вызывайте `t.Fatal` из отдельных горутин (Don't call `t.Fatal` from separate goroutines)
+
+Как [документировано в пакете testing](https://pkg.go.dev/testing#T),
+неправильно вызывать `t.FailNow`, `t.Fatal` и т.д. из любой горутины, кроме той,
+которая запускает функцию Test (или подтест). Если ваш тест запускает новые
+горутины, они не должны вызывать эти функции внутри этих горутин.
+
+[Тестовые помощники](#test-functions) обычно не сигнализируют о сбое из новых
+горутин, поэтому для них допустимо использовать `t.Fatal`. В случае сомнений
+вызовите `t.Error` и вернитесь.
+
+```go
+// Хорошо:
+func TestRevEngine(t *testing.T) {
+ engine, err := Start()
+ if err != nil {
+ t.Fatalf("Engine failed to start: %v", err)
+ }
+
+ num := 11
+ var wg sync.WaitGroup
+ wg.Add(num)
+ for i := 0; i < num; i++ {
+ go func() {
+ defer wg.Done()
+ if err := engine.Vroom(); err != nil {
+ // Здесь нельзя использовать t.Fatalf.
+ t.Errorf("No vroom left on engine: %v", err)
+ return
+ }
+ if rpm := engine.Tachometer(); rpm > 1e6 {
+ t.Errorf("Inconceivable engine rate: %d", rpm)
+ }
+ }()
+ }
+ wg.Wait()
+
+ if seen := engine.NumVrooms(); seen != num {
+ t.Errorf("engine.NumVrooms() = %d, want %d", seen, num)
+ }
+}
+```
+
+Добавление `t.Parallel` к тесту или подтесту не делает небезопасным вызов
+`t.Fatal`.
+
+Когда все вызовы API `testing` находятся в [тестовой функции](#test-functions),
+обычно легко заметить неправильное использование, потому что ключевое слово `go`
+легко увидеть. Передача аргументов `testing.T` усложняет отслеживание такого
+использования. Обычно причина передачи этих аргументов — введение тестового
+помощника, и они не должны зависеть от тестируемой системы. Поэтому, если
+тестовый помощник [регистрирует фатальную ошибку
+теста](#test-helper-error-handling), он может и должен делать это из горутины
+теста.
+
+<a id="t-field-names"></a>
+
+### Используйте имена полей в литералах структур (Use field names in struct literals)
+
+<a id="t-field-labels"></a>
+
+В табличных тестах предпочитайте указывать имена полей при инициализации
+литералов структур тестовых случаев. Это полезно, когда тестовые случаи
+охватывают большое вертикальное пространство (например, более 20-30 строк),
+когда есть соседние поля с одинаковым типом, а также когда вы хотите опустить
+поля, имеющие нулевое значение. Например:
+
+```go
+// Хорошо:
+func TestStrJoin(t *testing.T) {
+ tests := []struct {
+ slice []string
+ separator string
+ skipEmpty bool
+ want string
+ }{
+ {
+ slice: []string{"a", "b", ""},
+ separator: ",",
+ want: "a,b,",
+ },
+ {
+ slice: []string{"a", "b", ""},
+ separator: ",",
+ skipEmpty: true,
+ want: "a,b",
+ },
+ // ...
+ }
+ // ...
+}
+```
+
+<a id="t-common-setup-scope"></a>
+
+### Ограничивайте код настройки конкретными тестами (Keep setup code scoped to specific tests)
+
+По возможности настройка ресурсов и зависимостей должна быть максимально
+ограничена конкретными тестовыми случаями. Например, учитывая функцию настройки:
+
+```go
+// mustLoadDataSet загружает набор данных для тестов.
+//
+// Этот пример очень прост и легко читается. Часто реалистичная настройка более
+// сложная, подверженная ошибкам и потенциально медленная.
+func mustLoadDataset(t *testing.T) []byte {
+ t.Helper()
+ data, err := os.ReadFile("path/to/your/project/testdata/dataset")
+
+ if err != nil {
+ t.Fatalf("Could not load dataset: %v", err)
+ }
+ return data
+}
+```
+
+Вызовите `mustLoadDataset` явно в тестовых функциях, которые в этом нуждаются:
+
+```go
+// Хорошо:
+func TestParseData(t *testing.T) {
+ data := mustLoadDataset(t)
+ parsed, err := ParseData(data)
+ if err != nil {
+ t.Fatalf("Unexpected error parsing data: %v", err)
+ }
+ want := &DataTable{ /* ... */ }
+ if got := parsed; !cmp.Equal(got, want) {
+ t.Errorf("ParseData(data) = %v, want %v", got, want)
+ }
+}
+
+func TestListContents(t *testing.T) {
+ data := mustLoadDataset(t)
+ contents, err := ListContents(data)
+ if err != nil {
+ t.Fatalf("Unexpected error listing contents: %v", err)
+ }
+ want := []string{ /* ... */ }
+ if got := contents; !cmp.Equal(got, want) {
+ t.Errorf("ListContents(data) = %v, want %v", got, want)
+ }
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+```
+
+Тестовая функция `TestRegression682831` не использует набор данных и поэтому не
+вызывает `mustLoadDataset`, которая может быть медленной и подверженной сбоям:
+
+```go
+// Плохо:
+var dataset []byte
+
+func TestParseData(t *testing.T) {
+ // Как описано выше без вызова mustLoadDataset напрямую.
+}
+
+func TestListContents(t *testing.T) {
+ // Как описано выше без вызова mustLoadDataset напрямую.
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+
+func init() {
+ dataset = mustLoadDataset()
+}
+```
+
+Пользователь может захотеть запустить функцию изолированно от других и не должен
+быть наказан этими факторами:
+
+```shell
+# Нет причин для выполнения дорогой инициализации.
+$ go test -run TestRegression682831
+```
+
+<a id="t-custom-main"></a>
+
+#### Когда использовать пользовательскую точку входа `TestMain` (When to use a custom `TestMain` entrypoint)
+
+Если **все тесты в пакете** требуют общей настройки и **настройка требует
+очистки (teardown)**, вы можете использовать [пользовательскую точку входа
+testmain]. Это может произойти, если ресурс, требующийся тестовым случаям,
+особенно дорог в настройке, и стоимость должна быть амортизирована. Обычно к
+этому моменту вы уже убрали несвязанные тесты из набора тестов. Обычно это
+используется только для [функциональных тестов (functional tests)].
+
+Использование пользовательского `TestMain` **не должно быть вашим первым
+выбором** из-за количества осторожности, которое требуется для правильного
+использования. Сначала рассмотрите, достаточно ли решения в разделе
+[*амортизация общей настройки теста*] или обычного [тестового помощника] для
+ваших нужд.
+
+[пользовательскую точку входа testmain]:
+ https://golang.org/pkg/testing/#hdr-Main
+[функциональных тестов (functional tests)]:
+ https://en.wikipedia.org/wiki/Functional_testing
+[*амортизация общей настройки теста*]: #t-setup-amortization
+[тестового помощника]: #t-common-setup-scope
+
+```go
+// Хорошо:
+var db *sql.DB
+
+func TestInsert(t *testing.T) { /* omitted */ }
+
+func TestSelect(t *testing.T) { /* omitted */ }
+
+func TestUpdate(t *testing.T) { /* omitted */ }
+
+func TestDelete(t *testing.T) { /* omitted */ }
+
+// runMain устанавливает зависимости теста и в конечном итоге выполняет тесты.
+// Она определена как отдельная функция, чтобы этапы настройки могли четко
+// откладывать (defer) свои шаги очистки.
+func runMain(ctx context.Context, m *testing.M) (code int, err error) {
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ d, err := setupDatabase(ctx)
+ if err != nil {
+ return 0, err
+ }
+ defer d.Close() // Явно очищаем базу данных.
+ db = d // db определена как переменная на уровне пакета.
+
+ // m.Run() выполняет обычные, определенные пользователем тестовые функции.
+ // Любые операторы defer, которые были сделаны, будут выполнены после завершения m.Run().
+ return m.Run(), nil
+}
+
+func TestMain(m *testing.M) {
+ code, err := runMain(context.Background(), m)
+ if err != nil {
+ // Сообщения о сбоях должны записываться в STDERR, что и использует log.Fatal.
+ log.Fatal(err)
+ }
+ // ПРИМЕЧАНИЕ: операторы defer не выполняются после здесь из-за os.Exit
+ // завершающего процесс.
+ os.Exit(code)
+}
+```
+
+В идеале тестовый случай является герметичным (hermetic) между вызовами самого
+себя и между другими тестовыми случаями.
+
+По крайней мере, убедитесь, что отдельные тестовые случаи сбрасывают любое
+глобальное состояние, которое они изменили, если они это сделали (например, если
+тесты работают с внешней базой данных).
+
+<a id="t-setup-amortization"></a>
+
+#### Амортизация общей настройки теста (Amortizing common test setup)
+
+Использование `sync.Once` может быть уместным, хотя и не обязательно, если все
+из следующего верно для общей настройки:
+
+* Она дорогая.
+* Она применяется только к некоторым тестам.
+* Она не требует очистки.
+
+```go
+// Хорошо:
+var dataset struct {
+ once sync.Once
+ data []byte
+ err error
+}
+
+func mustLoadDataset(t *testing.T) []byte {
+ t.Helper()
+ dataset.once.Do(func() {
+ data, err := os.ReadFile("path/to/your/project/testdata/dataset")
+ // dataset определена как переменная на уровне пакета.
+ dataset.data = data
+ dataset.err = err
+ })
+ if err := dataset.err; err != nil {
+ t.Fatalf("Could not load dataset: %v", err)
+ }
+ return dataset.data
+}
+```
+
+Когда `mustLoadDataset` используется в нескольких тестовых функциях, ее
+стоимость амортизируется:
+
+```go
+// Хорошо:
+func TestParseData(t *testing.T) {
+ data := mustLoadDataset(t)
+
+ // Как описано выше.
+}
+
+func TestListContents(t *testing.T) {
+ data := mustLoadDataset(t)
+
+ // Как описано выше.
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+```
+
+Причина, по которой общая очистка сложна, заключается в том, что нет единого
+места для регистрации процедур очистки. Если функция настройки (в данном случае
+`mustLoadDataset`) полагается на контекст, `sync.Once` может быть
+проблематичным. Это потому, что второй из двух конкурентных вызовов функции
+настройки должен будет ждать завершения первого вызова, прежде чем вернуться.
+Этот период ожидания нельзя легко заставить уважать отмену контекста.
+
+<a id="string-concat"></a>
+
+## Конкатенация строк (String concatenation)
+
+Есть несколько способов конкатенации строк в Go. Некоторые примеры включают:
+
+* Оператор "+"
+* `fmt.Sprintf`
+* `strings.Builder`
+* `text/template`
+* `safehtml/template`
+
+Хотя не существует универсального правила, какой выбрать, следующие рекомендации
+описывают, когда каждый метод предпочтителен.
+
+<a id="string-concat-simple"></a>
+
+### Предпочитайте "+" для простых случаев (Prefer "+" for simple cases)
+
+Предпочитайте использовать "+" при конкатенации нескольких строк. Этот метод
+синтаксически самый простой и не требует импорта.
+
+```go
+// Хорошо:
+key := "projectid: " + p
+```
+
+<a id="string-concat-fmt"></a>
+
+### Предпочитайте `fmt.Sprintf` при форматировании (Prefer `fmt.Sprintf` when formatting)
+
+Предпочитайте использовать `fmt.Sprintf` при построении сложной строки с
+форматированием. Использование многих операторов "+" может затмить конечный
+результат.
+
+```go
+// Хорошо:
+str := fmt.Sprintf("%s [%s:%d]-> %s", src, qos, mtu, dst)
+```
+
+```go
+// Плохо:
+bad := src.String() + " [" + qos.String() + ":" + strconv.Itoa(mtu) + "]-> " + dst.String()
+```
+
+**Лучшая практика:** Когда результатом операции построения строки является
+`io.Writer`, не конструируйте временную строку с помощью `fmt.Sprintf`, чтобы
+просто отправить ее в Writer. Вместо этого используйте `fmt.Fprintf`, чтобы
+отправлять прямо в Writer.
+
+Когда форматирование еще сложнее, предпочитайте [`text/template`] или
+[`safehtml/template`] по мере необходимости.
+
+[`text/template`]: https://pkg.go.dev/text/template
+[`safehtml/template`]: https://pkg.go.dev/github.com/google/safehtml/template
+
+<a id="string-concat-piecemeal"></a>
+
+### Предпочитайте `strings.Builder` для построения строки по частям (Prefer `strings.Builder` for constructing a string piecemeal)
+
+Предпочитайте использовать `strings.Builder` при построении строки по частям.
+`strings.Builder` занимает амортизированное линейное время, тогда как "+" и
+`fmt.Sprintf` занимают квадратичное время при последовательном вызове для
+формирования большей строки.
+
+```go
+// Хорошо:
+b := new(strings.Builder)
+for i, d := range digitsOfPi {
+ fmt.Fprintf(b, "the %d digit of pi is: %d\n", i, d)
+}
+str := b.String()
+```
+
+**Примечание:** Для более подробного обсуждения см. [GoTip #29: Building
+Strings Efficiently](https://google.github.io/styleguide/go/index.html#gotip).
+
+<a id="string-constants"></a>
+
+### Константные строки (Constant strings)
+
+Предпочитайте использовать обратные кавычки (\`) при создании константных,
+многострочных строковых литералов.
+
+```go
+// Хорошо:
+usage := `Usage:
+
+custom_tool [args]`
+```
+
+```go
+// Плохо:
+usage := "" +
+ "Usage:\n" +
+ "\n" +
+ "custom_tool [args]"
+```
+
+<a id="globals"></a>
+
+## Глобальное состояние (Global state)
+
+Библиотеки не должны заставлять своих клиентов использовать API, которые
+полагаются на [глобальное состояние (global
+state)](https://en.wikipedia.org/wiki/Global_variable). Им рекомендуется не
+раскрывать API или экспортировать переменные на [уровне пакета (package level)],
+которые контролируют поведение для всех клиентов как часть их API. В остальной
+части раздела "глобальное" и "состояние на уровне пакета" используются как
+синонимы.
+
+Вместо этого, если ваша функциональность поддерживает состояние, позвольте вашим
+клиентам создавать и использовать экземпляры значений.
+
+**Важно:** Хотя это руководство применимо ко всем разработчикам, оно наиболее
+критично для поставщиков инфраструктуры, которые предлагают библиотеки,
+интеграции и сервисы другим командам.
+
+[глобальное состояние (global state)]:
+ https://en.wikipedia.org/wiki/Global_variable
+[уровне пакета (package level)]: https://go.dev/ref/spec#TopLevelDecl
+
+```go
+// Хорошо:
+// Пакет sidecar управляет подпроцессами, которые предоставляют функции для приложений.
+package sidecar
+
+type Registry struct { plugins map[string]*Plugin }
+
+func New() *Registry { return &Registry{plugins: make(map[string]*Plugin)} }
+
+func (r *Registry) Register(name string, p *Plugin) error { ... }
+```
+
+Ваши пользователи будут создавать необходимые им данные (`*sidecar.Registry`), а
+затем передавать их как явную зависимость:
+
+```go
+// Хорошо:
+package main
+
+func main() {
+ sidecars := sidecar.New()
+ if err := sidecars.Register("Cloud Logger", cloudlogger.New()); err != nil {
+ log.Exitf("Could not setup cloud logger: %v", err)
+ }
+ cfg := &myapp.Config{Sidecars: sidecars}
+ myapp.Run(context.Background(), cfg)
+}
+```
+
+Существуют разные подходы к миграции существующего кода для поддержки передачи
+зависимостей. Основной, который вы будете использовать, — передача зависимостей
+в качестве параметров конструкторам, функциям, методам или полям структур в
+цепочке вызовов.
+
+См. также:
+
+* [Go Tip #5: Slimming Your Client
+ Libraries](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #24: Use Case-Specific
+ Constructions](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #40: Improving Time Testability with Function
+ Parameters](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #41: Identify Function Call
+ Parameters](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #44: Improving Time Testability with Struct
+ Fields](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+
+API, которые не поддерживают явную передачу зависимостей, становятся хрупкими с
+увеличением числа клиентов:
+
+```go
+// Плохо:
+package sidecar
+
+var registry = make(map[string]*Plugin)
+
+func Register(name string, p *Plugin) error { /* регистрирует плагин в registry */ }
+```
+
+Рассмотрим, что происходит в случае тестов, проверяющих код, который транзитивно
+зависит от sidecar для облачного логирования.
+
+```go
+// Плохо:
+package app
+
+import (
+ "cloudlogger"
+ "sidecar"
+ "testing"
+)
+
+func TestEndToEnd(t *testing.T) {
+ // Система под тестом (SUT) полагается на sidecar для production облачного
+ // логгера, который уже зарегистрирован.
+ ... // Проверяем SUT и проверяем инварианты.
+}
+
+func TestRegression_NetworkUnavailability(t *testing.T) {
+ // У нас был сбой из-за сетевого раздела, который сделал облачный логгер
+ // неработоспособным, поэтому мы добавили регрессионный тест для проверки SUT с
+ // тестовым двойником, имитирующим недоступность сети для логгера.
+ sidecar.Register("cloudlogger", cloudloggertest.UnavailableLogger)
+ ... // Проверяем SUT и проверяем инварианты.
+}
+
+func TestRegression_InvalidUser(t *testing.T) {
+ // Система под тестом (SUT) полагается на sidecar для production облачного
+ // логгера, который уже зарегистрирован.
+ //
+ // Упс. cloudloggertest.UnavailableLogger все еще зарегистрирован с
+ // предыдущего теста.
+ ... // Проверяем SUT и проверяем инварианты.
+}
+```
+
+Тесты Go выполняются последовательно по умолчанию, поэтому вышеуказанные тесты
+выполняются как:
+
+1. `TestEndToEnd`
+2. `TestRegression_NetworkUnavailability`, который переопределяет значение по
+ умолчанию cloudlogger
+3. `TestRegression_InvalidUser`, который требует значения по умолчанию
+ cloudlogger, зарегистрированного в `package sidecar`
+
+Это создает тестовый случай, зависящий от порядка, что нарушает запуск с
+фильтрами тестов и не позволяет тестам запускаться параллельно или
+шардироваться.
+
+Использование глобального состояния создает проблемы, на которые нет простых
+ответов для вас и клиентов API:
+
+* Что произойдет, если клиенту нужно использовать разные и отдельно работающие
+ наборы `Plugin` (например, для поддержки нескольких серверов) в одном
+ процессе?
+
+* Что произойдет, если клиент захочет заменить зарегистрированный `Plugin`
+ альтернативной реализацией в тесте, например, [тестовым двойником]?
+
+ Что произойдет, если тестам клиента требуется герметичность между
+ экземплярами `Plugin` или между всеми зарегистрированными плагинами?
+
+* Что произойдет, если несколько клиентов `Register` плагин `Plugin` под одним
+ и тем же именем? Кто победит, если вообще победит?
+
+ Как следует [обрабатывать](https://neonxp.ru/pages/gostyleguide/google/decisions/#handle-errors) ошибки? Если код
+ вызывает panic или `log.Fatal`, будет ли это всегда [уместно для всех мест,
+ в которых может быть вызван API](https://neonxp.ru/pages/gostyleguide/google/decisions/#dont-panic)? Может ли клиент
+ проверить, что он не делает ничего плохого, прежде чем сделать это?
+
+* Существуют ли определенные этапы начальной загрузки программы или ее
+ жизненного цикла, во время которых можно вызывать `Register`, а когда нет?
+
+ Что произойдет, если `Register` будет вызван в неподходящее время? Клиент
+ может вызвать `Register` в [`func
+ init`](https://go.dev/ref/spec#Package_initialization), до разбора флагов
+ или после `main`. Этап, на котором вызывается функция, влияет на обработку
+ ошибок. Если автор API предполагает, что API вызывается *только* во время
+ инициализации программы без требования, чтобы это было так, это
+ предположение может подтолкнуть автора к проектированию обработки ошибок для
+ [завершения программы](https://neonxp.ru/pages/gostyleguide/google/best-practices/#program-init), моделируя API как
+ функцию типа `Must`. Завершение не подходит для библиотечных функций общего
+ назначения, которые могут использоваться на любом этапе.
+
+* Что, если потребности в параллелизме клиента и дизайнера не совпадают?
+
+См. также:
+
+* [Go Tip #36: Enclosing Package-Level
+ State](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #71: Reducing Parallel Test
+ Flakiness](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+* Обработка ошибок: [Look Before You
+ Leap](https://docs.python.org/3/glossary.html#term-LBYL) против [Easier to
+ Ask for Forgiveness than
+ Permission](https://docs.python.org/3/glossary.html#term-EAFP)
+* [Unit Testing Practices on Public APIs]
+
+Глобальное состояние имеет каскадные эффекты на [здоровье кодовой базы
+Google](https://neonxp.ru/pages/gostyleguide/google/guide/.md#maintainability). К глобальному состоянию следует подходить с
+**крайней тщательностью**.
+
+[Глобальное состояние бывает нескольких форм](#globals-forms), и вы можете
+использовать несколько [лакмусовых тестов, чтобы определить, когда оно
+безопасно](#globals-litmus-tests).
+
+[Unit Testing Practices on Public APIs]: index.md#unit-testing-practices
+
+<a id="globals-forms"></a>
+
+### Основные формы API состояния пакета (Major forms of package state APIs)
+
+Ниже перечислены несколько наиболее распространенных проблемных форм API:
+
+* Переменные верхнего уровня, независимо от того, экспортируются они или нет.
+
+ ```go
+ // Плохо:
+ package logger
+
+ // Sinks управляет выходными источниками по умолчанию для API логирования этого пакета.
+ // Эта переменная должна быть установлена во время инициализации пакета и никогда после этого.
+ var Sinks []Sink
+ ```
+
+ См. [лакмусовые тесты](#globals-litmus-tests), чтобы узнать, когда они
+ безопасны.
+
+* Шаблон [локатора служб (service locator
+ pattern)](https://en.wikipedia.org/wiki/Service_locator_pattern). См.
+ [первый пример](#globals). Сам шаблон локатора служб не является
+ проблематичным, а проблема в том, что локатор определен как глобальный.
+
+* Реестры для [обратных вызовов
+ (callbacks)](https://en.wikipedia.org/wiki/Callback_\(computer_programming\))
+ и подобного поведения.
+
+ ```go
+ // Плохо:
+ package health
+
+ var unhealthyFuncs []func
+
+ func OnUnhealthy(f func()) {
+ unhealthyFuncs = append(unhealthyFuncs, f)
+ }
+ ```
+
+* "Толстые" (thick) клиентские синглтоны для таких вещей, как бэкенды,
+ хранилища, уровни доступа к данным и другие системные ресурсы. Они часто
+ создают дополнительные проблемы с надежностью служб.
+
+ ```go
+ // Плохо:
+ package useradmin
+
+ var client pb.UserAdminServiceClientInterface
+
+ func Client() *pb.UserAdminServiceClient {
+ if client == nil {
+ client = ... // Настройка клиента.
+ }
+ return client
+ }
+ ```
+
+> **Примечание:** Многие устаревшие API в кодовой базе Google не следуют этому
+> руководству; фактически, некоторые стандартные библиотеки Go позволяют
+> настраивать поведение через глобальные значения. Тем не менее, нарушение
+> этого руководства устаревшим API **[не должно использоваться как
+> прецедент](https://neonxp.ru/pages/gostyleguide/google/guide/#local-consistency)** для продолжения шаблона.
+>
+> Лучше инвестировать в правильный дизайн API сегодня, чем платить за его
+> перепроектирование позже.
+
+<a id="globals-litmus-tests"></a>
+
+### Лакмусовые тесты (Litmus tests)
+
+[API, использующие шаблоны выше](#globals-forms), небезопасны, когда:
+
+* Несколько функций взаимодействуют через глобальное состояние при выполнении
+ в одной программе, несмотря на то, что в остальном они независимы (например,
+ написаны разными авторами в совершенно разных каталогах).
+* Независимые тестовые случаи взаимодействуют друг с другом через глобальное
+ состояние.
+* Пользователи API склонны заменять или подменять глобальное состояние для
+ целей тестирования, особенно чтобы заменить любую часть состояния [тестовым
+ двойником], например, заглушкой, фейком, шпионом или моком.
+* Пользователи должны учитывать особые требования к порядку при взаимодействии
+ с глобальным состоянием: `func init`, разобраны ли уже флаги и т.д.
+
+При условии, что вышеуказанные условия избегаются, существует **несколько
+ограниченных обстоятельств, при которых эти API безопасны**, а именно, когда
+верно любое из следующего:
+
+* Глобальное состояние логически постоянно
+ ([пример](https://github.com/klauspost/compress/blob/290f4cfacb3eff892555a491e3eeb569a48665e7/zstd/snappy.go#L413)).
+* Наблюдаемое поведение пакета является бессостоятельным (stateless).
+ Например, общедоступная функция может использовать частную глобальную
+ переменную в качестве кэша, но пока вызывающая сторона не может отличить
+ попадания в кэш от промахов, функция является бессостоятельной.
+* Глобальное состояние не просачивается в вещи, внешние по отношению к
+ программе, такие как sidecar-процессы или файлы в общей файловой системе.
+* Нет ожидания предсказуемого поведения
+ ([пример](https://pkg.go.dev/math/rand)).
+
+> **Примечание:**
+> [Sidecar-процессы](https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/ch02.html)
+> могут **не** быть строго локальными для процесса. Они могут и часто
+> используются совместно более чем одним процессом приложения. Более того, эти
+> sidecar часто взаимодействуют с внешними распределенными системами.
+>
+> Кроме того, те же правила бессостоятельности, идемпотентности и локальности в
+> дополнение к базовым соображениям выше применялись бы к коду самого
+> sidecar-процесса!
+
+Пример одной из таких безопасных ситуаций — [`package
+image`](https://pkg.go.dev/image) с его функцией
+[`image.RegisterFormat`](https://pkg.go.dev/image#RegisterFormat). Рассмотрим
+лакмусовые тесты, примененные к типичному декодеру, например, для обработки
+формата [PNG](https://pkg.go.dev/image/png):
+
+* Множественные вызовы API `package image`, использующие зарегистрированные
+ декодеры (например, `image.Decode`), не могут мешать друг другу, аналогично
+ и для тестов. Единственное исключение — `image.RegisterFormat`, но это
+ смягчается пунктами ниже.
+* Крайне маловероятно, что пользователь захочет заменить декодер [тестовым
+ двойником], так как декодер PNG является примером случая, когда предпочтение
+ нашей кодовой базы реальным объектам применяется. Однако пользователь с
+ большей вероятностью заменит декодер тестовым двойником, если декодер
+ состоятельно взаимодействует с ресурсами операционной системы (например,
+ сетью).
+* Коллизии при регистрации возможны, хотя на практике они, вероятно, редки.
+* Декодеры являются бессостоятельными, идемпотентными и чистыми (pure).
+
+<a id="globals-default-instance"></a>
+
+### Предоставление экземпляра по умолчанию (Providing a default instance)
+
+Хотя и не рекомендуется, допустимо предоставить упрощенный API, использующий
+состояние на уровне пакета, если вам нужно максимизировать удобство для
+пользователя.
+
+Следуйте [лакмусовым тестам](#globals-litmus-tests) с этими рекомендациями в
+таких случаях:
+
+1. Пакет должен предлагать клиентам возможность создавать изолированные
+ экземпляры типов пакета, как [описано выше](#globals-forms).
+2. Общедоступные API, использующие глобальное состояние, должны быть тонкой
+ прослойкой (thin proxy) к предыдущему API. Хороший пример этого —
+ [`http.Handle`](https://pkg.go.dev/net/http#Handle), внутренне вызывающий
+ [`(*http.ServeMux).Handle`](https://pkg.go.dev/net/http#ServeMux.Handle) на
+ переменной пакета
+ [`http.DefaultServeMux`](https://pkg.go.dev/net/http#DefaultServeMux).
+3. Этот API уровня пакета должен использоваться только [целями сборки
+ бинарников (binary build targets)], а не [библиотеками (libraries)], если
+ только библиотеки не предпринимают рефакторинг для поддержки передачи
+ зависимостей. Инфраструктурные библиотеки, которые могут быть импортированы
+ другими пакетами, не должны полагаться на состояние на уровне пакета
+ импортируемых ими пакетов.
+
+ Например, поставщик инфраструктуры, реализующий sidecar, который должен
+ использоваться совместно с другими командами, использующими API сверху,
+ должен предложить API для этого:
+
+ ```go
+ // Хорошо:
+ package cloudlogger
+
+ func New() *Logger { ... }
+
+ func Register(r *sidecar.Registry, l *Logger) {
+ r.Register("Cloud Logging", l)
+ }
+ ```
+
+4. Этот API уровня пакета должен [документировать](#documentation-conventions)
+ и обеспечивать соблюдение своих инвариантов (например, на каком этапе
+ жизненного цикла программы его можно вызывать, можно ли использовать его
+ параллельно). Кроме того, он должен предоставлять API для сброса глобального
+ состояния к известному хорошему значению по умолчанию (например, для
+ облегчения тестирования).
+
+[целями сборки бинарников (binary build targets)]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_binary
+[библиотеками (libraries)]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_library
+
+См. также:
+
+* [Go Tip #36: Enclosing Package-Level
+ State](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
diff --git a/content/pages/gostyleguide/google/decisions.md b/content/pages/gostyleguide/google/decisions.md
new file mode 100644
index 0000000..acb17b7
--- /dev/null
+++ b/content/pages/gostyleguide/google/decisions.md
@@ -0,0 +1,4057 @@
+---
+order: 2
+title: Google Go Style Guide — Решения
+---
+
+# Решения по стилю Go
+
+Оригинал: https://google.github.io/styleguide/go/decisions
+
+[Обзор](https://neonxp.ru/pages/gostyleguide/google/) | [Руководство](https://neonxp.ru/pages/gostyleguide/google/guide) | [Решения](https://neonxp.ru/pages/gostyleguide/google/decisions) |
+[Лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices)
+
+
+**Примечание:** Это часть серии документов, описывающих [Стиль Go](https://neonxp.ru/pages/gostyleguide/google/) в
+Google. Этот документ является **[нормативным](https://neonxp.ru/pages/gostyleguide/google/#normative), но не
+[каноническим](https://neonxp.ru/pages/gostyleguide/google/#canonical)** и подчиняется [основному руководству по
+стилю](https://neonxp.ru/pages/gostyleguide/google/guide/). Подробнее см. [в обзоре](https://neonxp.ru/pages/gostyleguide/google/#about).
+
+<a id="about"></a>
+
+## Об этом документе
+
+В этом документе содержатся решения по стилю, призванные унифицировать и дать
+стандартные рекомендации, пояснения и примеры для советов, которые дают
+наставники по читаемости Go.
+
+Этот документ **не является исчерпывающим** и будет пополняться со временем. В
+случаях, когда [основное руководство по стилю](https://neonxp.ru/pages/gostyleguide/google/guide/) противоречит приведенным
+здесь рекомендациям, **руководство по стилю имеет приоритет**, и этот документ
+должен быть обновлен соответственно.
+
+Полный набор документов по стилю Go см. в
+[Обзоре](https://google.github.io/styleguide/go#about).
+
+Следующие разделы были перемещены из "Решений по стилю" в другие части
+руководства:
+
+* **MixedCaps**: см. [guide#mixed-caps](https://neonxp.ru/pages/gostyleguide/google/guide/#mixed-caps) <a
+ id="mixed-caps"></a>
+
+* **Форматирование**: см. [guide#formatting](https://neonxp.ru/pages/gostyleguide/google/guide/#formatting) <a
+ id="formatting"></a>
+
+* **Длина строки**: см. [guide#line-length](https://neonxp.ru/pages/gostyleguide/google/guide/#line-length) <a
+ id="line-length"></a>
+
+<a id="naming"></a>
+
+## Именование
+
+Общие рекомендации по именованию см. в разделе об именовании в [основном
+руководстве по стилю](https://neonxp.ru/pages/gostyleguide/google/guide/#naming). Следующие разделы дают дальнейшие
+разъяснения по конкретным областям именования.
+
+<a id="underscores"></a>
+
+### Подчеркивания
+
+Имена в Go, как правило, не должны содержать подчеркиваний. Существует три
+исключения из этого принципа:
+
+1. Имена пакетов, которые импортируются только сгенерированным кодом, могут
+ содержать подчеркивания. Подробнее о том, как выбирать имена многословных
+ пакетов, см. в разделе [имена пакетов](#package-names).
+1. Имена тестовых (`Test`), бенчмарк (`Benchmark`) и примеров (`Example`)
+ функций в файлах `*_test.go` могут содержать подчеркивания.
+1. Низкоуровневые библиотеки, взаимодействующие с операционной системой или
+ cgo, могут повторно использовать идентификаторы, как это сделано в
+ [`syscall`]. Ожидается, что это будет очень редко встречаться в большинстве
+ кодовых баз.
+
+**Примечание:** Имена файлов исходного кода не являются идентификаторами Go и не
+должны следовать этим соглашениям. Они могут содержать подчеркивания.
+
+[`syscall`]: https://pkg.go.dev/syscall#pkg-constants
+
+<a id="package-names"></a>
+
+### Имена пакетов
+
+<a id="TOC-PackageNames"></a>
+
+В Go имена пакетов должны быть краткими и использовать только строчные буквы и
+цифры (например, [`k8s`], [`oauth2`]). Многословные имена пакетов должны
+оставаться целыми и в нижнем регистре (например, [`tabwriter`] вместо
+`tabWriter`, `TabWriter` или `tab_writer`).
+
+Избегайте выбора имен пакетов, которые могут быть [затенены] часто используемыми
+локальными именами переменных. Например, `usercount` — лучшее имя пакета, чем
+`count`, так как `count` — часто используемое имя переменной.
+
+Имена пакетов Go не должны содержать подчеркиваний. Если вам нужно импортировать
+пакет, который содержит их в своем имени (обычно из сгенерированного или
+стороннего кода), его необходимо переименовать при импорте в имя, подходящее для
+использования в коде Go.
+
+Исключением является то, что имена пакетов, которые импортируются только
+сгенерированным кодом, могут содержать подчеркивания. Конкретные примеры
+включают:
+
+* Использование суффикса `_test` для модульных тестов, проверяющих только
+ экспортированный API пакета (пакет `testing` называет это ["черным
+ ящиком"](https://pkg.go.dev/testing)). Например, пакет `linkedlist` должен
+ определять свои модульные тесты "черного ящика" в пакете с именем
+ `linkedlist_test` (не `linked_list_test`)
+
+* Использование подчеркиваний и суффикса `_test` для пакетов, содержащих
+ функциональные или интеграционные тесты. Например, интеграционный тест
+ сервиса связного списка может называться `linked_list_service_test`
+
+* Использование суффикса `_test` для [примеров документации на уровне
+ пакета](https://go.dev/blog/examples)
+
+[`tabwriter`]: https://pkg.go.dev/text/tabwriter
+[`k8s`]: https://pkg.go.dev/k8s.io/client-go/kubernetes
+[`oauth2`]: https://pkg.go.dev/golang.org/x/oauth2
+[shadowed]: best-practices#shadowing
+
+Избегайте неинформативных имен пакетов, таких как `util`, `utility`, `common`,
+`helper`, `model`, `testhelper` и т.д., которые могут побуждать пользователей
+пакета [переименовывать его при импорте](#import-renaming). См.:
+
+* [Рекомендации по так называемым "служебным
+ пакетам"](https://neonxp.ru/pages/gostyleguide/google/best-practices/#util-packages)
+* [Go Tip #97: Что в
+ имени](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #108: Сила хорошего имени
+ пакета](https://google.github.io/styleguide/go/index.html#gotip)
+
+Когда импортированный пакет переименовывается (например, `import foopb
+"path/to/foo_go_proto"`), локальное имя пакета должно соответствовать правилам
+выше, так как локальное имя определяет, как на символы в пакете ссылаются в
+файле. Если данный импорт переименован в нескольких файлах, особенно в одном и
+том же или соседних пакетах, по возможности следует использовать одно и то же
+локальное имя для согласованности.
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+См. также: [Пост в блоге Go об именах
+пакетов](https://go.dev/blog/package-names).
+
+<a id="receiver-names"></a>
+
+### Имена получателей (Receiver)
+
+<a id="TOC-ReceiverNames"></a>
+
+Имена [получателей] (receiver) должны быть:
+
+* Краткими (обычно одна или две буквы)
+* Сокращениями для самого типа
+* Применяться последовательно для каждого получателя этого типа
+
+Длинное имя | Лучшее имя
+----------------------------- | -------------------------
+`func (tray Tray)` | `func (t Tray)`
+`func (info *ResearchInfo)` | `func (ri *ResearchInfo)`
+`func (this *ReportWriter)` | `func (w *ReportWriter)`
+`func (self *Scanner)` | `func (s *Scanner)`
+
+[получателей]: https://golang.org/ref/spec#Method_declarations
+
+<a id="constant-names"></a>
+
+### Имена констант
+
+Имена констант должны использовать [MixedCaps], как и все остальные имена в Go.
+([Экспортируемые] константы начинаются с заглавной буквы, а неэкспортируемые —
+со строчной.) Это применимо, даже если это нарушает соглашения в других языках.
+Имена констант не должны быть производными от их значений и должны объяснять,
+что означает это значение.
+
+```go
+// Хорошо:
+const MaxPacketSize = 512
+
+const (
+ ExecuteBit = 1 << iota
+ WriteBit
+ ReadBit
+)
+```
+
+[MixedCaps]: guide#mixed-caps
+[Экспортируемые]: https://tour.golang.org/basics/3
+
+Не используйте имена констант не в стиле MixedCaps или константы с префиксом
+`K`.
+
+```go
+// Плохо:
+const MAX_PACKET_SIZE = 512
+const kMaxBufferSize = 1024
+const KMaxUsersPergroup = 500
+```
+
+Называйте константы в соответствии с их ролью, а не значениями. Если у константы
+нет роли, кроме ее значения, то нет необходимости определять ее как константу.
+
+```go
+// Плохо:
+const Twelve = 12
+
+const (
+ UserNameColumn = "username"
+ GroupColumn = "group"
+)
+```
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="initialisms"></a>
+
+### Аббревиатуры и акронимы (Initialisms)
+
+<a id="TOC-Initialisms"></a>
+
+Слова в именах, которые являются аббревиатурами или акронимами (например, `URL`
+и `NATO`), должны иметь одинаковый регистр. `URL` должен появляться как `URL`
+или `url` (как в `urlPony` или `URLPony`), но никогда как `Url`. Как общее
+правило, идентификаторы (например, `ID` и `DB`) также должны быть написаны с
+заглавной буквы, аналогично их использованию в английской прозе.
+
+* В именах с несколькими аббревиатурами (например, `XMLAPI`, потому что оно
+ содержит `XML` и `API`) каждая буква в данной аббревиатуре должна иметь один
+ регистр, но каждая аббревиатура в имени не обязана иметь одинаковый регистр.
+* В именах с аббревиатурой, содержащей строчную букву (например, `DDoS`,
+ `iOS`, `gRPC`), аббревиатура должна отображаться, как в стандартной прозе,
+ если только вам не нужно изменить первую букву ради [экспортируемости
+ (exportedness)]. В этих случаях вся аббревиатура должна быть в одном
+ регистре (например, `ddos`, `IOS`, `GRPC`).
+
+[экспортируемости (exportedness)]:
+ https://golang.org/ref/spec#Exported_identifiers
+
+<!-- Keep this table narrow. If it must grow wider, replace with a list. -->
+
+Использование в английском | Область видимости | Правильно | Неправильно
+-------------------------- | ----------------- | --------- | --------------------------------------
+XML API | Экспортировано | `XMLAPI` | `XmlApi`, `XMLApi`, `XmlAPI`, `XMLapi`
+XML API | Не экспортировано | `xmlAPI` | `xmlapi`, `xmlApi`
+iOS | Экспортировано | `IOS` | `Ios`, `IoS`
+iOS | Не экспортировано | `iOS` | `ios`
+gRPC | Экспортировано | `GRPC` | `Grpc`
+gRPC | Не экспортировано | `gRPC` | `grpc`
+DDoS | Экспортировано | `DDoS` | `DDOS`, `Ddos`
+DDoS | Не экспортировано | `ddos` | `dDoS`, `dDOS`
+ID | Экспортировано | `ID` | `Id`
+ID | Не экспортировано | `id` | `iD`
+DB | Экспортировано | `DB` | `Db`
+DB | Не экспортировано | `db` | `dB`
+Txn | Экспортировано | `Txn` | `TXN`
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="getters"></a>
+
+### Геттеры (Getters)
+
+<a id="TOC-Getters"></a>
+
+Имена функций и методов не должны использовать префикс `Get` или `get`, если
+только базовое понятие не использует слово "get" (например, HTTP GET).
+Предпочитайте начинать имя с существительного напрямую, например, используйте
+`Counts` вместо `GetCounts`.
+
+Если функция включает выполнение сложных вычислений или удаленного вызова,
+вместо `Get` можно использовать другое слово, например `Compute` или `Fetch`,
+чтобы читателю было ясно, что вызов функции может занять время и может
+блокироваться или завершиться неудачей.
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="variable-names"></a>
+
+### Имена переменных
+
+<a id="TOC-VariableNames"></a>
+
+Общее эмпирическое правило заключается в том, что длина имени должна быть
+пропорциональна размеру его области видимости и обратно пропорциональна
+количеству раз, которое оно используется в этой области. Переменная, созданная
+на уровне файла, может потребовать несколько слов, тогда как переменная в
+области видимости одного внутреннего блока может быть одним словом или даже
+всего одним-двумя символами, чтобы сохранить код понятным и избежать лишней
+информации.
+
+Вот приблизительный базовый уровень. Эти численные рекомендации не являются
+строгими правилами. Применяйте суждение, основанное на контексте, [ясности] и
+[лаконичности].
+
+* Малая область видимости — это область, в которой выполняется одна или две
+ небольшие операции, скажем, 1-7 строк.
+* Средняя область видимости — это несколько небольших или одна большая
+ операция, скажем, 8-15 строк.
+* Большая область видимости — это одна или несколько больших операций, скажем,
+ 15-25 строк.
+* Очень большая область видимости — это все, что занимает больше страницы
+ (скажем, более 25 строк).
+
+[ясности]: guide#clarity
+[лаконичности]: guide#concision
+
+Имя, которое может быть совершенно понятным (например, `c` для счетчика) в
+маленькой области видимости, может оказаться недостаточным в большей области и
+потребует уточнения, чтобы напомнить читателю о его назначении дальше по коду.
+Область видимости, в которой много переменных или переменных, представляющих
+похожие значения или понятия, может потребовать более длинных имен переменных,
+чем предполагает область видимости.
+
+Специфичность понятия также может помочь сохранить имя переменной кратким.
+Например, если используется только одна база данных, короткое имя переменной
+вроде `db`, которое обычно зарезервировано для очень малых областей видимости,
+может оставаться совершенно понятным даже при очень большой области видимости. В
+этом случае одно слово `database`, вероятно, приемлемо в зависимости от размера
+области видимости, но не обязательно, поскольку `db` — очень распространенное
+сокращение для этого слова с малым количеством альтернативных интерпретаций.
+
+Имя локальной переменной должно отражать то, что она содержит, и как она
+используется в текущем контексте, а не откуда взялось значение. Например, часто
+бывает, что лучшее локальное имя переменной не совпадает с именем поля структуры
+или поля protobuf.
+
+В общем:
+
+* Однобуквенные имена, такие как `count` или `options`, — хорошая отправная
+ точка.
+* Дополнительные слова могут быть добавлены для различения похожих имен,
+ например `userCount` и `projectCount`.
+* Не просто выбрасывайте буквы, чтобы сэкономить на печати. Например,
+ `Sandbox` предпочтительнее, чем `Sbx`, особенно для экспортируемых имен.
+* Опускайте [типы и слова, похожие на типы] из большинства имен переменных.
+ * Для числа `userCount` — лучшее имя, чем `numUsers` или `usersInt`.
+ * Для среза `users` — лучшее имя, чем `userSlice`.
+ * Допустимо включать квалификатор, похожий на тип, если в области
+ видимости есть две версии значения, например, у вас может быть ввод,
+ сохраненный в `ageString`, и `age` для распарсенного значения.
+* Опускайте слова, которые ясны из [окружающего контекста]. Например, в
+ реализации метода `UserCount` локальная переменная с именем `userCount`,
+ вероятно, избыточна; `count`, `users` или даже `c` так же читаемы.
+
+[типы и слова, похожие на типы]: #repetitive-with-type
+[окружающего контекста]: #repetitive-in-context
+
+<a id="v"></a>
+
+#### Однобуквенные имена переменных
+
+Однобуквенные имена переменных могут быть полезным инструментом для минимизации
+[повторов](#repetition), но также могут сделать код излишне непрозрачным.
+Ограничьте их использование случаями, когда полное слово очевидно и где было бы
+излишне повторять его вместо однобуквенной переменной.
+
+В общем:
+
+* Для [переменной-получателя метода] предпочтительно одно- или двухбуквенное
+ имя.
+* Использование знакомых имен переменных для распространенных типов часто
+ полезно:
+ * `r` для `io.Reader` или `*http.Request`
+ * `w` для `io.Writer` или `http.ResponseWriter`
+* Однобуквенные идентификаторы допустимы в качестве целочисленных переменных
+ цикла, особенно для индексов (например, `i`) и координат (например, `x` и
+ `y`).
+* Сокращения могут быть допустимыми идентификаторами цикла, если область
+ видимости короткая, например `for _, n := range nodes { ... }`.
+
+[переменной-получателя метода]: #receiver-names
+
+<a id="repetition"></a>
+
+### Повторы
+
+<!--
+Примечание для будущих редакторов:
+
+Не используйте термин "stutter" (заикание) для обозначения случаев, когда имя повторяется.
+-->
+
+Исходный код Go должен избегать ненужных повторов. Один из распространенных
+источников этого — повторяющиеся имена, которые часто включают ненужные слова
+или повторяют свой контекст или тип. Сам код также может быть излишне
+повторяющимся, если один и тот же или похожий сегмент кода появляется несколько
+раз в непосредственной близости.
+
+Повторяющееся именование может принимать многие формы, включая:
+
+<a id="repetitive-with-package"></a>
+
+#### Имя пакета vs. экспортируемый символ
+
+При именовании экспортируемых символов имя пакета всегда видно за пределами
+вашего пакета, поэтому избыточную информацию между ними следует сократить или
+устранить. Если пакет экспортирует только один тип, и он назван в честь самого
+пакета, каноническое имя для конструктора — `New`, если он требуется.
+
+> **Примеры:** Повторяющееся имя -> Лучшее имя
+>
+> * `widget.NewWidget` -> `widget.New`
+> * `widget.NewWidgetWithName` -> `widget.NewWithName`
+> * `db.LoadFromDatabase` -> `db.Load`
+> * `goatteleportutil.CountGoatsTeleported` -> `gtutil.CountGoatsTeleported`
+> или `goatteleport.Count`
+> * `myteampb.MyTeamMethodRequest` -> `mtpb.MyTeamMethodRequest` или
+> `myteampb.MethodRequest`
+
+<a id="repetitive-with-type"></a>
+
+#### Имя переменной vs. тип
+
+Компилятор всегда знает тип переменной, и в большинстве случаев читателю также
+понятен тип переменной по тому, как она используется. Уточнять тип переменной
+необходимо только если ее значение появляется дважды в одной и той же области
+видимости.
+
+Повторяющееся имя | Лучшее имя
+------------------------------- | ----------------------
+`var numUsers int` | `var users int`
+`var nameString string` | `var name string`
+`var primaryProject *Project` | `var primary *Project`
+
+Если значение появляется в нескольких формах, это можно уточнить либо с помощью
+дополнительного слова, например `raw` и `parsed`, либо с помощью базового
+представления:
+
+```go
+// Хорошо:
+limitRaw := r.FormValue("limit")
+limit, err := strconv.Atoi(limitRaw)
+```
+
+```go
+// Хорошо:
+limitStr := r.FormValue("limit")
+limit, err := strconv.Atoi(limitStr)
+```
+
+<a id="repetitive-in-context"></a>
+
+#### Внешний контекст vs. локальные имена
+
+Имена, включающие информацию из окружающего их контекста, часто создают лишний
+шум без пользы. Имя пакета, имя метода, имя типа, имя функции, путь импорта и
+даже имя файла могут предоставить контекст, который автоматически квалифицирует
+все имена внутри.
+
+```go
+// Плохо:
+// В пакете "ads/targeting/revenue/reporting"
+type AdsTargetingRevenueReport struct{}
+
+func (p *Project) ProjectName() string
+```
+
+```go
+// Хорошо:
+// В пакете "ads/targeting/revenue/reporting"
+type Report struct{}
+
+func (p *Project) Name() string
+```
+
+```go
+// Плохо:
+// В пакете "sqldb"
+type DBConnection struct{}
+```
+
+```go
+// Хорошо:
+// В пакете "sqldb"
+type Connection struct{}
+```
+
+```go
+// Плохо:
+// В пакете "ads/targeting"
+func Process(in *pb.FooProto) *Report {
+ adsTargetingID := in.GetAdsTargetingID()
+}
+```
+
+```go
+// Хорошо:
+// В пакете "ads/targeting"
+func Process(in *pb.FooProto) *Report {
+ id := in.GetAdsTargetingID()
+}
+```
+
+Повторение, как правило, следует оценивать в контексте использования символа, а
+не изолированно. Например, следующий код содержит множество имен, которые могут
+быть хороши в некоторых обстоятельствах, но избыточны в контексте:
+
+```go
+// Плохо:
+func (db *DB) UserCount() (userCount int, err error) {
+ var userCountInt64 int64
+ if dbLoadError := db.LoadFromDatabase("count(distinct users)", &userCountInt64); dbLoadError != nil {
+ return 0, fmt.Errorf("failed to load user count: %s", dbLoadError)
+ }
+ userCount = int(userCountInt64)
+ return userCount, nil
+}
+```
+
+Вместо этого информацию об именах, которые ясны из контекста или использования,
+часто можно опустить:
+
+```go
+// Хорошо:
+func (db *DB) UserCount() (int, error) {
+ var count int64
+ if err := db.Load("count(distinct users)", &count); err != nil {
+ return 0, fmt.Errorf("failed to load user count: %s", err)
+ }
+ return int(count), nil
+}
+```
+
+<a id="commentary"></a>
+
+## Комментарии
+
+Соглашения, касающиеся комментариев (что комментировать, какой стиль
+использовать, как предоставлять исполняемые примеры и т.д.), предназначены для
+поддержки удобства чтения документации публичного API. Подробнее см. [Effective
+Go](http://golang.org/doc/effective_go.html#commentary).
+
+Раздел о [соглашениях по документации] в документе о лучших практиках
+рассматривает это подробнее.
+
+**Лучшая практика:** Используйте [предпросмотр документации (doc preview)] во
+время разработки и проверки кода, чтобы увидеть, является ли документация и
+исполняемые примеры полезными и представлены ли они так, как вы ожидаете.
+
+**Совет:** Godoc использует очень мало специального форматирования; списки и
+фрагменты кода обычно должны иметь отступ, чтобы избежать переноса строк. Кроме
+отступа, декорации, как правило, следует избегать.
+
+[предпросмотр документации (doc preview)]: best-practices#documentation-preview
+[соглашениям по документации]: best-practices#documentation-conventions
+
+<a id="comment-line-length"></a>
+
+### Длина строк комментария
+
+Убедитесь, что комментарии читаемы из исходного кода даже на узких экранах.
+
+Когда комментарий становится слишком длинным, рекомендуется разбить его на
+несколько однострочных комментариев. По возможности стремитесь к комментариям,
+которые будут хорошо читаться на терминале шириной 80 колонок, однако это не
+жесткий предел; в Go нет фиксированного ограничения длины строки для
+комментариев. Стандартная библиотека, например, часто предпочитает разрывать
+комментарий по пунктуации, что иногда оставляет отдельные строки ближе к отметке
+60-70 символов.
+
+Существует множество существующего кода, в котором комментарии превышают 80
+символов в длину. Эти рекомендации не следует использовать как оправдание для
+изменения такого кода в ходе проверки на читаемость (см.
+[согласованность](https://neonxp.ru/pages/gostyleguide/google/guide/#consistency)), хотя командам рекомендуется использовать
+возможность обновлять комментарии в соответствии с этим руководством в рамках
+других рефакторингов. Основная цель этого руководства — гарантировать, что все
+наставники по читаемости Go дают одинаковые рекомендации, когда и если
+рекомендации даются.
+
+Подробнее о комментариях см. в [этом посте из блога Go о документации].
+
+[этом посте из блога Go о документации]:
+ https://blog.golang.org/godoc-documenting-go-code
+
+```text
+# Хорошо:
+// Это абзац комментария.
+// Длина отдельных строк не имеет значения в Godoc;
+// но выбор переноса делает его легко читаемым на узких экранах.
+//
+// Не беспокойтесь слишком о длинном URL:
+// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
+//
+// Аналогично, если у вас есть другая информация, которая становится неудобной
+// из-за слишком большого количества разрывов строк, используйте свое суждение и включите длинную строку,
+// если она помогает, а не мешает.
+```
+
+Избегайте комментариев, которые будут многократно переноситься на маленьких
+экранах, что ухудшает удобство чтения.
+
+```text
+# Плохо:
+// Это абзац комментария. Длина отдельных строк не имеет значения в Godoc;
+// но выбор переноса создает неровные строки на узких экранах или при просмотре кода,
+// что может раздражать, особенно в блоке комментариев, который будет переноситься
+// многократно.
+//
+// Не беспокойтесь слишком о длинном URL:
+// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
+```
+
+<a id="doc-comments"></a>
+
+### Документирующие комментарии (Doc comments)
+
+<a id="TOC-DocComments"></a>
+
+Все экспортируемые имена верхнего уровня должны иметь документирующие
+комментарии, как и неэкспортируемые объявления типов или функций с неочевидным
+поведением или смыслом. Эти комментарии должны быть [полными предложениями],
+которые начинаются с имени описываемого объекта. Артикль ("a", "an", "the")
+может предшествовать имени, чтобы оно читалось более естественно.
+
+```go
+// Хорошо:
+// Request представляет запрос на выполнение команды.
+type Request struct { ...
+
+// Encode записывает JSON-кодировку req в w.
+func Encode(w io.Writer, req *Request) { ...
+```
+
+Документирующие комментарии появляются в [Godoc](https://pkg.go.dev/) и
+выводятся в IDE, поэтому их следует писать для всех, кто использует пакет.
+
+[полными предложениями]: #comment-sentences
+
+Документирующий комментарий относится к следующему символу или группе полей,
+если он появляется в структуре.
+
+```go
+// Хорошо:
+// Options настраивает сервис управления группами.
+type Options struct {
+ // Общая настройка:
+ Name string
+ Group *FooGroup
+
+ // Зависимости:
+ DB *sql.DB
+
+ // Кастомизация:
+ LargeGroupThreshold int // опционально; по умолчанию: 10
+ MinimumMembers int // опционально; по умолчанию: 2
+}
+```
+
+**Лучшая практика:** Если у вас есть документирующие комментарии для
+неэкспортируемого кода, следуйте тому же обычаю, как если бы он был
+экспортируемым (а именно, начиная комментарий с неэкспортируемого имени). Это
+упрощает его экспорт позже простой заменой неэкспортируемого имени на новое
+экспортируемое в комментариях и коде.
+
+<a id="comment-sentences"></a>
+
+### Предложения в комментариях
+
+<a id="TOC-CommentSentences"></a>
+
+Комментарии, которые являются полными предложениями, должны начинаться с
+заглавной буквы и заканчиваться пунктуацией, как стандартные английские
+предложения. (В качестве исключения можно начать предложение с
+некапитализированного имени идентификатора, если оно иначе понятно. Такие
+случаи, вероятно, лучше делать только в начале абзаца.)
+
+Комментарии, которые являются фрагментами предложений, не имеют таких требований
+к пунктуации или капитализации.
+
+[Документирующие комментарии] всегда должны быть полными предложениями, и
+поэтому всегда должны начинаться с заглавной буквы и заканчиваться пунктуацией.
+Простые комментарии в конце строки (особенно для полей структуры) могут быть
+простыми фразами, которые предполагают, что имя поля является подлежащим.
+
+```go
+// Хорошо:
+// Server обрабатывает подачу цитат из собрания сочинений Шекспира.
+type Server struct {
+ // BaseDir указывает на базовый каталог, в котором хранятся работы Шекспира.
+ //
+ // Ожидается следующая структура каталога:
+ // {BaseDir}/manifest.json
+ // {BaseDir}/{name}/{name}-part{number}.txt
+ BaseDir string
+
+ WelcomeMessage string // отображается при входе пользователя
+ ProtocolVersion string // проверяется для входящих запросов
+ PageLength int // строк на странице при печати (опционально; по умолчанию: 20)
+}
+```
+
+[Документирующие комментарии]: #doc-comments
+
+<a id="examples"></a>
+
+### Примеры
+
+<a id="TOC-Examples"></a>
+
+Пакеты должны четко документировать предполагаемое использование. Постарайтесь
+предоставить [исполняемый пример]; примеры появляются в Godoc. Исполняемые
+примеры принадлежат тестовому файлу, а не файлу исходного кода продакшена.
+Смотрите этот пример ([Godoc], [исходный код]).
+
+[исполняемый пример]: http://blog.golang.org/examples
+[Godoc]: https://pkg.go.dev/time#example-Duration
+[исходный код]:
+ https://cs.opensource.google/go/go/+/HEAD:src/time/example_test.go
+
+Если предоставить исполняемый пример нецелесообразно, пример кода может быть
+предоставлен внутри комментариев к коду. Как и другие фрагменты кода и командной
+строки в комментариях, он должен следовать стандартным соглашениям
+форматирования.
+
+<a id="named-result-parameters"></a>
+
+### Именованные возвращаемые параметры
+
+<a id="TOC-NamedResultParameters"></a>
+
+При именовании параметров учитывайте, как сигнатуры функций отображаются в
+Godoc. Имя самой функции и тип возвращаемых параметров часто достаточно ясны.
+
+```go
+// Хорошо:
+func (n *Node) Parent1() *Node
+func (n *Node) Parent2() (*Node, error)
+```
+
+Если функция возвращает два или более параметра одного типа, добавление имен
+может быть полезным.
+
+```go
+// Хорошо:
+func (n *Node) Children() (left, right *Node, err error)
+```
+
+Если вызывающая сторона должна выполнить действие над определенными
+возвращаемыми параметрами, их именование может помочь подсказать, какое это
+действие:
+
+```go
+// Хорошо:
+// WithTimeout возвращает контекст, который будет отменен не позднее, чем через длительность d
+// от текущего момента.
+//
+// Вызывающая сторона должна обеспечить вызов возвращенной функции cancel, когда
+// контекст больше не нужен, чтобы предотвратить утечку ресурсов.
+func WithTimeout(parent Context, d time.Duration) (ctx Context, cancel func())
+```
+
+В приведенном выше коде отмена — это конкретное действие, которое должна
+выполнить вызывающая сторона. Однако, если бы возвращаемые параметры были
+записаны просто как `(Context, func())`, было бы неясно, что подразумевается под
+"функцией отмены".
+
+Не используйте именованные возвращаемые параметры, когда имена создают [ненужные
+повторы](#repetitive-with-type).
+
+```go
+// Плохо:
+func (n *Node) Parent1() (node *Node)
+func (n *Node) Parent2() (node *Node, err error)
+```
+
+Не называйте возвращаемые параметры, чтобы избежать объявления переменной внутри
+функции. Эта практика приводит к излишней многословности API в обмен на
+незначительную краткость реализации.
+
+[Голые возвраты (Naked returns)] допустимы только в маленькой функции. Как
+только это функция среднего размера, будьте явны со своими возвращаемыми
+значениями. Аналогично, не называйте возвращаемые параметры только потому, что
+это позволяет вам использовать голые возвраты. [Ясность](https://neonxp.ru/pages/gostyleguide/google/guide/#clarity) всегда
+важнее, чем сэкономить несколько строк в вашей функции.
+
+Всегда приемлемо назвать возвращаемый параметр, если его значение должно быть
+изменено в отложенном замыкании.
+
+> **Совет:** Типы часто могут быть понятнее, чем имена в сигнатурах функций.
+> [GoTip #38: Функции как именованные типы] демонстрирует это.
+>
+> В [`WithTimeout`] выше, реальный код использует [`CancelFunc`] вместо простого
+> `func()` в списке возвращаемых параметров и требует небольших усилий для
+> документирования.
+
+[Голые возвраты (Naked returns)]: https://tour.golang.org/basics/7
+[GoTip #38: Функции как именованные типы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[`WithTimeout`]: https://pkg.go.dev/context#WithTimeout
+[`CancelFunc`]: https://pkg.go.dev/context#CancelFunc
+
+<a id="package-comments"></a>
+
+### Комментарии к пакетам
+
+<a id="TOC-PackageComments"></a>
+
+Комментарии к пакету должны появляться непосредственно перед объявлением пакета,
+без пустой строки между комментарием и именем пакета. Пример:
+
+```go
+// Хорошо:
+// Package math предоставляет базовые константы и математические функции.
+//
+// Этот пакет не гарантирует битовой идентичности результатов на разных архитектурах.
+package math
+```
+
+Должен быть ровно один комментарий к пакету на пакет. Если пакет состоит из
+нескольких файлов, ровно в одном из файлов должен быть комментарий к пакету.
+
+Комментарии для пакетов `main` имеют немного другую форму, где имя правила
+`go_binary` в BUILD-файле заменяет имя пакета.
+
+```go
+// Хорошо:
+// Команда seed_generator — это утилита, которая генерирует файл сида Finch
+// из набора JSON-конфигураций исследований.
+package main
+```
+
+Другие стили комментариев допустимы, если имя бинарника точно такое же, как
+написано в BUILD-файле. Когда имя бинарника — первое слово, его заглавная буква
+обязательна, даже если оно не совпадает с написанием вызова в командной строке.
+
+```go
+// Хорошо:
+// Binary seed_generator ...
+// Command seed_generator ...
+// Program seed_generator ...
+// The seed_generator command ...
+// The seed_generator program ...
+// Seed_generator ...
+```
+
+Советы:
+
+* Примеры вызовов командной строки и использования API могут быть полезной
+ документацией. Для форматирования Godoc сделайте отступ для строк
+ комментария, содержащих код.
+
+* Если нет очевидного основного файла или если комментарий к пакету необычайно
+ длинный, допустимо поместить документирующий комментарий в файл с именем
+ `doc.go`, содержащий только комментарий и объявление пакета.
+
+* Многострочные комментарии могут использоваться вместо нескольких
+ однострочных. Это в первую очередь полезно, если документация содержит
+ разделы, которые могут быть полезны для копирования и вставки из исходного
+ файла, как, например, примеры командной строки (для бинарников) и примеры
+ шаблонов.
+
+ ```go
+ // Хорошо:
+ /*
+ Команда seed_generator — это утилита, которая генерирует файл сида Finch
+ из набора JSON-конфигураций исследований.
+
+ seed_generator *.json | base64 > finch-seed.base64
+ */
+ package template
+ ```
+
+* Комментарии, предназначенные для сопровождающих и относящиеся ко всему
+ файлу, обычно размещаются после объявлений импорта. Они не отображаются в
+ Godoc и не подчиняются приведенным выше правилам для комментариев к пакетам.
+
+<a id="imports"></a>
+
+## Импорты
+
+<a id="TOC-Imports"></a>
+
+<a id="import-renaming"></a>
+
+### Переименование импортов
+
+Импорты пакетов обычно не должны переименовываться, но есть случаи, когда они
+должны быть переименованы или когда переименование улучшает читаемость.
+
+Локальные имена для импортированных пакетов должны следовать [рекомендациям по
+именованию пакетов](#package-names), включая запрет на использование
+подчеркиваний и заглавных букв. Старайтесь быть
+[последовательными](https://neonxp.ru/pages/gostyleguide/google/guide/#consistency), всегда используя одно и то же локальное
+имя для одного и того же импортированного пакета.
+
+Импортированный пакет *должен* быть переименован, чтобы избежать конфликта имен
+с другими импортами. (Следствие из этого: [хорошие имена
+пакетов](#package-names) не должны требовать переименования.) В случае конфликта
+имен предпочтительнее переименовать наиболее локальный или специфичный для
+проекта импорт.
+
+Сгенерированные пакеты протоколов буфера *должны* быть переименованы, чтобы
+удалить подчеркивания из их имен, и их локальные имена должны иметь суффикс
+`pb`. Подробнее см. [лучшие практики для proto и заглушек
+(stubs)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#import-protos).
+
+```go
+// Хорошо:
+import (
+ foosvcpb "path/to/package/foo_service_go_proto"
+)
+```
+
+Наконец, импортированный, несгенерированный пакет *может* быть переименован,
+если он имеет неинформативное имя (например, `util` или `v1`) Делайте это
+экономно: не переименовывайте пакет, если код, окружающий использование пакета,
+передает достаточно контекста. По возможности предпочитайте рефакторинг самого
+пакета с более подходящим именем.
+
+```go
+// Хорошо:
+import (
+ core "github.com/kubernetes/api/core/v1"
+ meta "github.com/kubernetes/apimachinery/pkg/apis/meta/v1beta1"
+)
+```
+
+Если вам нужно импортировать пакет, имя которого конфликтует с распространенным
+локальным именем переменной, которое вы хотите использовать (например, `url`,
+`ssh`), и вы хотите переименовать пакет, предпочтительный способ сделать это —
+использовать суффикс `pkg` (например, `urlpkg`). Обратите внимание, что
+возможно затенение пакета локальной переменной; это переименование необходимо
+только если пакет все еще нужно использовать, когда такая переменная находится в
+области видимости.
+
+<a id="import-grouping"></a>
+
+### Группировка импортов
+
+Импорты должны быть организованы в следующие группы по порядку:
+
+1. Пакеты стандартной библиотеки
+
+1. Другие пакеты (проекта и вендорные)
+
+1. Импорты Protocol Buffer (например, `fpb "path/to/foo_go_proto"`)
+
+1. Импорт для [побочных эффектов (side
+ effects)](https://go.dev/doc/effective_go#blank_import) (например, `_
+ "path/to/package"`)
+
+```go
+// Хорошо:
+package main
+
+import (
+ "fmt"
+ "hash/adler32"
+ "os"
+
+ "github.com/dsnet/compress/flate"
+ "golang.org/x/text/encoding"
+ "google.golang.org/protobuf/proto"
+
+ foopb "myproj/foo/proto/proto"
+
+ _ "myproj/rpc/protocols/dial"
+ _ "myproj/security/auth/authhooks"
+)
+```
+
+<a id="import-blank"></a>
+
+### "Пустой" импорт (`import _`)
+
+<a id="TOC-ImportBlank"></a>
+
+Пакеты, которые импортируются только для их побочных эффектов (используя
+синтаксис `import _ "package"`), могут импортироваться только в главном пакете
+(`main`) или в тестах, которые требуют их.
+
+Некоторые примеры таких пакетов:
+
+* [time/tzdata](https://pkg.go.dev/time/tzdata)
+
+* [image/jpeg](https://pkg.go.dev/image/jpeg) в коде обработки изображений
+
+Избегайте пустых импортов в библиотечных пакетах, даже если библиотека косвенно
+зависит от них. Ограничение импортов с побочными эффектами главным пакетом
+помогает контролировать зависимости и позволяет писать тесты, которые полагаются
+на другой импорт без конфликта или лишних затрат на сборку.
+
+Следующие исключения являются единственными для этого правила:
+
+* Вы можете использовать пустой импорт, чтобы обойти проверку на запрещенные
+ импорты в [статическом анализаторе nogo].
+
+* Вы можете использовать пустой импорт пакета
+ [embed](https://pkg.go.dev/embed) в исходном файле, который использует
+ директиву компилятора `//go:embed`.
+
+**Совет:** Если вы создаете библиотечный пакет, который косвенно зависит от
+импорта с побочным эффектом в продакшене, задокументируйте предполагаемое
+использование.
+
+[статическом анализаторе nogo]:
+ https://github.com/bazelbuild/rules_go/blob/master/go/nogo.rst
+
+<a id="import-dot"></a>
+
+### "Точечный" импорт (`import .`)
+
+<a id="TOC-ImportDot"></a>
+
+Форма `import .` — это языковая возможность, позволяющая переносить
+идентификаторы, экспортированные из другого пакета, в текущий пакет без
+квалификации. Подробнее см. [спецификацию
+языка](https://go.dev/ref/spec#Import_declarations).
+
+Не **используйте** эту возможность в кодовой базе Google; это затрудняет
+понимание, откуда взялась функциональность.
+
+```go
+// Плохо:
+package foo_test
+
+import (
+ "bar/testutil" // также импортирует "foo"
+ . "foo"
+)
+
+var myThing = Bar() // Bar определен в пакете foo; квалификация не нужна.
+```
+
+```go
+// Хорошо:
+package foo_test
+
+import (
+ "bar/testutil" // также импортирует "foo"
+ "foo"
+)
+
+var myThing = foo.Bar()
+```
+
+<a id="errors"></a>
+
+## Ошибки
+
+<a id="returning-errors"></a>
+
+### Возврат ошибок
+
+<a id="TOC-ReturningErrors"></a>
+
+Используйте `error` для сигнализации о том, что функция может завершиться
+неудачей. По соглашению, `error` — последний параметр результата.
+
+```go
+// Хорошо:
+func Good() error { /* ... */ }
+```
+
+Возврат `nil` в качестве ошибки — идиоматический способ сигнализировать об
+успешной операции, которая в противном случае могла бы завершиться неудачей.
+Если функция возвращает ошибку, вызывающие стороны должны рассматривать все
+не-ошибочные возвращаемые значения как неопределенные, если явно не
+задокументировано иначе. Обычно не-ошибочные возвращаемые значения являются их
+нулевыми значениями, но на это нельзя полагаться.
+
+```go
+// Хорошо:
+func GoodLookup() (*Result, error) {
+ // ...
+ if err != nil {
+ return nil, err
+ }
+ return res, nil
+}
+```
+
+Экспортируемые функции, возвращающие ошибки, должны возвращать их, используя тип
+`error`. Конкретные типы ошибок подвержены тонким ошибкам: конкретный `nil`
+указатель может быть завернут в интерфейс и таким образом стать ненулевым
+значением (см. [FAQ по Go на эту тему][nil error]).
+
+```go
+// Плохо:
+func Bad() *os.PathError { /*...*/ }
+```
+
+**Совет:** Функция, принимающая аргумент [`context.Context`], обычно должна
+возвращать `error`, чтобы вызывающая сторона могла определить, была ли отменена
+контекст во время выполнения функции.
+
+[nil error]: https://golang.org/doc/faq#nil_error
+
+<a id="error-strings"></a>
+
+### Строки ошибок
+
+<a id="TOC-ErrorStrings"></a>
+
+Строки ошибок не должны начинаться с заглавной буквы (если только не начинаются
+с экспортируемого имени, имени собственного или аббревиатуры) и не должны
+заканчиваться пунктуацией. Это потому что строки ошибок обычно появляются внутри
+другого контекста перед выводом пользователю.
+
+```go
+// Плохо:
+err := fmt.Errorf("Something bad happened.")
+```
+
+```go
+// Хорошо:
+err := fmt.Errorf("something bad happened")
+```
+
+С другой стороны, стиль для полного отображаемого сообщения (логирование,
+неудача теста, ответ API или другой UI) зависит от ситуации, но обычно должен
+быть написан с заглавной буквы.
+
+```go
+// Хорошо:
+log.Infof("Operation aborted: %v", err)
+log.Errorf("Operation aborted: %v", err)
+t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)
+```
+
+<a id="handle-errors"></a>
+
+### Обработка ошибок
+
+<a id="TOC-HandleErrors"></a>
+
+Код, который сталкивается с ошибкой, должен принять осознанное решение о том,
+как ее обработать. Обычно нецелесообразно отбрасывать ошибки, используя
+переменные `_`. Если функция возвращает ошибку, сделайте одно из следующих
+действий:
+
+* Обработайте и исправьте ошибку немедленно.
+* Верните ошибку вызывающей стороне.
+* В исключительных ситуациях вызовите [`log.Fatal`] или (если абсолютно
+ необходимо) `panic`.
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+В редких обстоятельствах, когда уместно игнорировать или отбросить ошибку
+(например, вызов [`(*bytes.Buffer).Write`], который, как задокументировано,
+никогда не завершается неудачей), сопровождающий комментарий должен объяснять,
+почему это безопасно.
+
+```go
+// Хорошо:
+var b *bytes.Buffer
+
+n, _ := b.Write(p) // никогда не возвращает ненулевую ошибку
+```
+
+Для дальнейшего обсуждения и примеров обработки ошибок см. [Effective
+Go](http://golang.org/doc/effective_go.html#errors) и [лучшие
+практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/.md#error-handling).
+
+[`(*bytes.Buffer).Write`]: https://pkg.go.dev/bytes#Buffer.Write
+
+<a id="in-band-errors"></a>
+
+### Ошибки "в потоке" (In-band errors)
+
+<a id="TOC-In-Band-Errors"></a>
+
+В C и подобных языках распространено, что функции возвращают значения, такие как
+-1, null или пустую строку, чтобы сигнализировать об ошибках или отсутствующих
+результатах. Это известно как обработка ошибок "в потоке".
+
+```go
+// Плохо:
+// Lookup возвращает значение для ключа или -1, если нет соответствия для ключа.
+func Lookup(key string) int
+```
+
+Неспособность проверить значение ошибки "в потоке" может привести к ошибкам и
+может приписать ошибки не той функции.
+
+```go
+// Плохо:
+// Следующая строка возвращает ошибку, что Parse завершился неудачей для входного значения,
+// тогда как на самом деле неудача в том, что нет соответствия для missingKey.
+return Parse(Lookup(missingKey))
+```
+
+Поддержка Go нескольких возвращаемых значений предоставляет лучшее решение (см.
+[раздел Effective Go о множественных возвращаемых значениях]). Вместо того
+чтобы требовать от клиентов проверять значение ошибки "в потоке", функция должна
+возвращать дополнительное значение, чтобы указать, являются ли ее другие
+возвращаемые значения валидными. Это возвращаемое значение может быть ошибкой
+или булевым значением, когда объяснение не нужно, и должно быть последним
+возвращаемым значением.
+
+```go
+// Хорошо:
+// Lookup возвращает значение для ключа или ok=false, если нет соответствия для ключа.
+func Lookup(key string) (value string, ok bool)
+```
+
+Такой API предотвращает ошибочное написание вызывающей стороной
+`Parse(Lookup(key))`, что вызывает ошибку времени компиляции, поскольку
+`Lookup(key)` имеет 2 вывода.
+
+Возврат ошибок таким образом способствует более надежной и явной обработке
+ошибок:
+
+```go
+// Хорошо:
+value, ok := Lookup(key)
+if !ok {
+ return fmt.Errorf("no value for %q", key)
+}
+return Parse(value)
+```
+
+Некоторые функции стандартной библиотеки, например в пакете `strings`,
+возвращают значения ошибок "в потоке". Это значительно упрощает код для
+манипуляций со строками за счет необходимости большей внимательности от
+программиста. В целом, код Go в кодовой базе Google должен возвращать
+дополнительные значения для ошибок.
+
+[раздел Effective Go о множественных возвращаемых значениях]:
+ http://golang.org/doc/effective_go.html#multiple-returns
+
+<a id="indent-error-flow"></a>
+
+### Отступы для потока ошибок
+
+<a id="TOC-IndentErrorFlow"></a>
+
+Обрабатывайте ошибки перед продолжением выполнения остального кода. Это улучшает
+читаемость кода, позволяя читателю быстро найти нормальный путь выполнения. Эта
+же логика применяется к любому блоку, который проверяет условие, а затем
+завершается терминальным условием (например, `return`, `panic`, `log.Fatal`).
+
+Код, который выполняется, если терминальное условие не выполнено, должен
+появляться после блока `if` и не должен быть вложен в предложение `else`.
+
+```go
+// Хорошо:
+if err != nil {
+ // обработка ошибки
+ return // или continue, и т.д.
+}
+// нормальный код
+```
+
+```go
+// Плохо:
+if err != nil {
+ // обработка ошибки
+} else {
+ // нормальный код, который выглядит ненормальным из-за отступа
+}
+```
+
+> **Совет:** Если вы используете переменную более чем для нескольких строк кода,
+> как правило, не стоит использовать стиль `if` с инициализатором. В этих
+> случаях обычно лучше вынести объявление наружу и использовать стандартный `if`
+> оператор:
+>
+> ```go
+> // Хорошо:
+> x, err := f()
+> if err != nil {
+> // обработка ошибки
+> return
+> }
+> // много кода, который использует x
+> // на нескольких строках
+> ```
+>
+> ```go
+> // Плохо:
+> if x, err := f(); err != nil {
+> // обработка ошибки
+> return
+> } else {
+> // много кода, который использует x
+> // на нескольких строках
+> }
+> ```
+
+Подробнее см. [Go Tip #1: Линия видимости (Line of Sight)] и [TotT: Reduce Code
+Complexity by Reducing
+Nesting](https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html).
+
+[Go Tip #1: Линия видимости (Line of Sight)]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="language"></a>
+
+## Язык
+
+<a id="literal-formatting"></a>
+
+### Форматирование литералов
+
+Go обладает исключительно мощным [синтаксисом составных литералов], с помощью
+которого можно выражать глубоко вложенные, сложные значения одним выражением.
+По возможности следует использовать этот синтаксис литералов вместо построения
+значений поле за полем. Форматирование `gofmt` для литералов, как правило,
+довольно хорошее, но существуют некоторые дополнительные правила для сохранения
+читаемости и поддерживаемости этих литералов.
+
+[синтаксисом составных литералов]:
+ https://golang.org/ref/spec#Composite_literals
+
+<a id="literal-field-names"></a>
+
+#### Имена полей
+
+Литералы структур должны указывать **имена полей** для типов, определенных вне
+текущего пакета.
+
+* Включайте имена полей для типов из других пакетов.
+
+ ```go
+ // Хорошо:
+ // https://pkg.go.dev/encoding/csv#Reader
+ r := csv.Reader{
+ Comma: ',',
+ Comment: '#',
+ FieldsPerRecord: 4,
+ }
+ ```
+
+ Положение полей в структуре и полный набор полей (оба из которых необходимо
+ указать правильно, когда имена полей опущены) обычно не считаются частью
+ публичного API структуры; указание имени поля необходимо, чтобы избежать
+ ненужной связи.
+
+ ```go
+ // Плохо:
+ r := csv.Reader{',', '#', 4, false, false, false, false}
+ ```
+
+* Для локальных типов пакета имена полей не обязательны.
+
+ ```go
+ // Хорошо:
+ okay := Type{42}
+ also := internalType{4, 2}
+ ```
+
+ Имена полей все же следует использовать, если это делает код яснее, и это
+ очень распространено. Например, структуру с большим количеством полей почти
+ всегда следует инициализировать с указанием имен полей.
+
+ <!-- TODO: Maybe a better example here that doesn't have many fields. -->
+
+ ```go
+ // Хорошо:
+ okay := StructWithLotsOfFields{
+ field1: 1,
+ field2: "two",
+ field3: 3.14,
+ field4: true,
+ }
+ ```
+
+<a id="literal-matching-braces"></a>
+
+#### Совпадение скобок
+
+Закрывающая часть пары фигурных скобок всегда должна появляться на строке с
+таким же количеством отступов, как и открывающая скобка. Однострочные литералы
+обязательно имеют это свойство. Когда литерал занимает несколько строк,
+сохранение этого свойства сохраняет соответствие скобок для литералов таким же,
+как соответствие скобок для обычных синтаксических конструкций Go, таких как
+функции и операторы `if`.
+
+Самая распространенная ошибка в этой области — ставить закрывающую скобку на той
+же строке, что и значение в многострочном литерале структуры. В этих случаях
+строка должна заканчиваться запятой, а закрывающая скобка должна появляться на
+следующей строке.
+
+```go
+// Хорошо:
+good := []*Type\{\{Key: "value"\}\}
+```
+
+```go
+// Хорошо:
+good := []*Type{
+ {Key: "multi"},
+ {Key: "line"},
+}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ {Key: "multi"},
+ {Key: "line"}}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ {
+ Key: "value"},
+}
+```
+
+<a id="literal-cuddled-braces"></a>
+
+#### "Объединенные" скобки (Cuddled braces)
+
+Удаление пробела между фигурными скобками (так называемое "объединение" их) для
+литералов срезов и массивов допустимо только когда оба следующих условия
+истинны.
+
+* [Отступы совпадают](#literal-matching-braces)
+* Внутренние значения также являются литералами или сборщиками protobuf (то
+ есть не переменной или другим выражением)
+
+```go
+// Хорошо:
+good := []*Type{
+ { // Не объединены
+ Field: "value",
+ },
+ {
+ Field: "value",
+ },
+}
+```
+
+```go
+// Хорошо:
+good := []*Type{\{ // Правильно объединены
+ Field: "value",
+}, {
+ Field: "value",
+}\}
+```
+
+```go
+// Хорошо:
+good := []*Type{
+ first, // Не могут быть объединены
+ {Field: "second"},
+}
+```
+
+```go
+// Хорошо:
+okay := []*pb.Type{pb.Type_builder{
+ Field: "first", // Сборщики Proto могут быть объединены для экономии вертикального пространства
+}.Build(), pb.Type_builder{
+ Field: "second",
+}.Build()}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ first,
+ {
+ Field: "second",
+ }\}
+```
+
+<a id="literal-repeated-type-names"></a>
+
+#### Повторяющиеся имена типов
+
+Повторяющиеся имена типов могут быть опущены в литералах срезов и карт (map).
+Это может помочь уменьшить загромождение. Приемлемый случай для явного
+повторения имен типов — это работа со сложным типом, который не является
+распространенным в вашем проекте, когда повторяющиеся имена типов находятся на
+строках, далеко отстоящих друг от друга, и могут напоминать читателю о
+контексте.
+
+```go
+// Хорошо:
+good := []*Type{
+ {A: 42},
+ {A: 43},
+}
+```
+
+```go
+// Плохо:
+repetitive := []*Type{
+ &Type{A: 42},
+ &Type{A: 43},
+}
+```
+
+```go
+// Хорошо:
+good := map[Type1]*Type2{
+ {A: 1}: {B: 2},
+ {A: 3}: {B: 4},
+}
+```
+
+```go
+// Плохо:
+repetitive := map[Type1]*Type2{
+ Type1{A: 1}: &Type2{B: 2},
+ Type1{A: 3}: &Type2{B: 4},
+}
+```
+
+**Совет:** Если вы хотите удалить повторяющиеся имена типов в литералах
+структур, вы можете запустить `gofmt -s`.
+
+<a id="literal-zero-value-fields"></a>
+
+#### Поля с нулевыми значениями
+
+[Нулевые] поля могут быть опущены в литералах структур, когда ясность не
+теряется в результате.
+
+Хорошо спроектированные API часто используют конструкцию с нулевыми значениями
+для улучшенной читаемости. Например, опускание трех полей с нулевыми значениями
+из следующей структуры привлекает внимание к единственной опции, которая
+указана.
+
+[Нулевые]: https://golang.org/ref/spec#The_zero_value
+
+```go
+// Плохо:
+import (
+ "github.com/golang/leveldb"
+ "github.com/golang/leveldb/db"
+)
+
+ldb := leveldb.Open("/my/table", &db.Options{
+ BlockSize: 1<<16,
+ ErrorIfDBExists: true,
+
+ // Все эти поля имеют свои нулевые значения.
+ BlockRestartInterval: 0,
+ Comparer: nil,
+ Compression: nil,
+ FileSystem: nil,
+ FilterPolicy: nil,
+ MaxOpenFiles: 0,
+ WriteBufferSize: 0,
+ VerifyChecksums: false,
+})
+```
+
+```go
+// Хорошо:
+import (
+ "github.com/golang/leveldb"
+ "github.com/golang/leveldb/db"
+)
+
+ldb := leveldb.Open("/my/table", &db.Options{
+ BlockSize: 1<<16,
+ ErrorIfDBExists: true,
+})
+```
+
+Структуры в табличных тестах часто выигрывают от [явных имен полей], особенно
+когда тестовая структура нетривиальна. Это позволяет автору опускать поля с
+нулевыми значениями полностью, когда рассматриваемые поля не относятся к
+тестовому случаю. Например, успешные тестовые случаи должны опускать любые поля,
+связанные с ошибками или неудачами. В случаях, когда нулевое значение
+необходимо для понимания тестового случая, таких как тестирование нулевых или
+`nil` входных данных, имена полей должны быть указаны.
+
+[явных имен полей]: #literal-field-names
+
+**Лаконично**
+
+```go
+tests := []struct {
+ input string
+ wantPieces []string
+ wantErr error
+}{
+ {
+ input: "1.2.3.4",
+ wantPieces: []string{"1", "2", "3", "4"},
+ },
+ {
+ input: "hostname",
+ wantErr: ErrBadHostname,
+ },
+}
+```
+
+**Явно**
+
+```go
+tests := []struct {
+ input string
+ wantIPv4 bool
+ wantIPv6 bool
+ wantErr bool
+}{
+ {
+ input: "1.2.3.4",
+ wantIPv4: true,
+ wantIPv6: false,
+ },
+ {
+ input: "1:2::3:4",
+ wantIPv4: false,
+ wantIPv6: true,
+ },
+ {
+ input: "hostname",
+ wantIPv4: false,
+ wantIPv6: false,
+ wantErr: true,
+ },
+}
+```
+
+<a id="nil-slices"></a>
+
+### Nil-срезы
+
+Для большинства целей нет функциональной разницы между `nil` и пустым срезом.
+Встроенные функции, такие как `len` и `cap`, ведут себя ожидаемо на `nil`
+срезах.
+
+```go
+// Хорошо:
+import "fmt"
+
+var s []int // nil
+
+fmt.Println(s) // []
+fmt.Println(len(s)) // 0
+fmt.Println(cap(s)) // 0
+for range s {...} // нет операции
+
+s = append(s, 42)
+fmt.Println(s) // [42]
+```
+
+Если вы объявляете пустой срез как локальную переменную (особенно если она может
+быть источником возвращаемого значения), предпочитайте инициализацию nil, чтобы
+снизить риск ошибок со стороны вызывающих.
+
+```go
+// Хорошо:
+var t []string
+```
+
+```go
+// Плохо:
+t := []string{}
+```
+
+Не создавайте API, которые вынуждают своих клиентов делать различие между nil и
+пустым срезом.
+
+```go
+// Хорошо:
+// Ping проверяет доступность своих целей.
+// Возвращает хосты, которые успешно ответили.
+func Ping(hosts []string) ([]string, error) { ... }
+```
+
+```go
+// Плохо:
+// Ping проверяет доступность своих целей и возвращает список хостов,
+// которые успешно ответили. Может быть пустым, если вход был пустым.
+// nil означает, что произошла системная ошибка.
+func Ping(hosts []string) []string { ... }
+```
+
+При проектировании интерфейсов избегайте различия между `nil` срезом и ненулевым
+срезом нулевой длины, так как это может привести к тонким программным ошибкам.
+Обычно это достигается с помощью `len` для проверки на пустоту, а не `== nil`.
+
+Эта реализация принимает как `nil`, так и срезы нулевой длины как "пустые":
+
+```go
+// Хорошо:
+// describeInts описывает s с заданным префиксом, если s не пуст.
+func describeInts(prefix string, s []int) {
+ if len(s) == 0 {
+ return
+ }
+ fmt.Println(prefix, s)
+}
+```
+
+Вместо того чтобы полагаться на различие как часть API:
+
+```go
+// Плохо:
+func maybeInts() []int { /* ... */ }
+
+// describeInts описывает s с заданным префиксом; передайте nil, чтобы полностью пропустить.
+func describeInts(prefix string, s []int) {
+ // Поведение этой функции непреднамеренно меняется в зависимости от того, что
+ // возвращает maybeInts() в 'пустых' случаях (nil или []int{}).
+ if s == nil {
+ return
+ }
+ fmt.Println(prefix, s)
+}
+
+describeInts("Here are some ints:", maybeInts())
+```
+
+См. [ошибки "в потоке"] для дальнейшего обсуждения.
+
+[ошибки "в потоке"]: #in-band-errors
+
+<a id="indentation-confusion"></a>
+
+### Путаница с отступами
+
+Избегайте введения разрыва строки, если это приведет к выравниванию остальной
+части строки с вложенным блоком кода. Если этого невозможно избежать, оставьте
+пробел, чтобы отделить код в блоке от перенесенной строки.
+
+```go
+// Плохо:
+if longCondition1 && longCondition2 &&
+ // Условия 3 и 4 имеют тот же отступ, что и код внутри if.
+ longCondition3 && longCondition4 {
+ log.Info("all conditions met")
+}
+```
+
+См. следующие разделы для конкретных рекомендаций и примеров:
+
+* [Форматирование функций](#func-formatting)
+* [Условные операторы и циклы](#conditional-formatting)
+* [Форматирование литералов](#literal-formatting)
+
+<a id="func-formatting"></a>
+
+### Форматирование функций
+
+Сигнатура объявления функции или метода должна оставаться на одной строке, чтобы
+избежать [путаницы с отступами](#indentation-confusion).
+
+Списки аргументов функций могут создавать одни из самых длинных строк в файле
+исходного кода Go. Однако они предшествуют изменению отступа, и поэтому трудно
+разорвать строку так, чтобы последующие строки не выглядели частью тела функции
+запутанным образом:
+
+```go
+// Плохо:
+func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
+ foo4, foo5, foo6 int) {
+ foo7 := bar(foo1)
+ // ...
+}
+```
+
+См. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#funcargs) для нескольких вариантов
+сокращения мест вызовов функций, которые в противном случае имели бы много
+аргументов.
+
+Строки часто можно сократить, вынося локальные переменные.
+
+```go
+// Хорошо:
+local := helper(some, parameters, here)
+good := foo.Call(list, of, parameters, local)
+```
+
+Аналогично, вызовы функций и методов не должны разделяться исключительно на
+основе длины строки.
+
+```go
+// Хорошо:
+good := foo.Call(long, list, of, parameters, all, on, one, line)
+```
+
+```go
+// Плохо:
+bad := foo.Call(long, list, of, parameters,
+ with, arbitrary, line, breaks)
+```
+
+По возможности избегайте добавления встроенных комментариев к конкретным
+аргументам функций. Вместо этого используйте [структуру опций (option
+struct)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#option-structure) или добавьте больше деталей в
+документацию функции.
+
+```go
+// Хорошо:
+good := server.New(ctx, server.Options{Port: 42})
+```
+
+```go
+// Плохо:
+bad := server.New(
+ ctx,
+ 42, // Port
+)
+```
+
+Если API нельзя изменить или если локальный вызов необычен (независимо от того,
+слишком длинный вызов или нет), всегда допустимо добавлять разрывы строк, если
+это помогает понять вызов.
+
+```go
+// Хорошо:
+canvas.RenderHeptagon(fillColor,
+ x0, y0, vertexColor0,
+ x1, y1, vertexColor1,
+ x2, y2, vertexColor2,
+ x3, y3, vertexColor3,
+ x4, y4, vertexColor4,
+ x5, y5, vertexColor5,
+ x6, y6, vertexColor6,
+)
+```
+
+Обратите внимание, что строки в приведенном выше примере не переносятся на
+определенную границу столбца, а группируются на основе координат вершин и цвета.
+
+Длинные строковые литералы внутри функций не должны разбиваться ради длины
+строки. Для функций, включающих такие строки, разрыв строки можно добавить
+после формата строки, а аргументы могут быть предоставлены на следующей или
+последующих строках. Решение о том, где должны быть разрывы строк, лучше всего
+принимать на основе семантических групп входных данных, а не исключительно на
+основе длины строки.
+
+```go
+// Хорошо:
+log.Warningf("Database key (%q, %d, %q) incompatible in transaction started by (%q, %d, %q)",
+ currentCustomer, currentOffset, currentKey,
+ txCustomer, txOffset, txKey)
+```
+
+```go
+// Плохо:
+log.Warningf("Database key (%q, %d, %q) incompatible in"+
+ " transaction started by (%q, %d, %q)",
+ currentCustomer, currentOffset, currentKey, txCustomer,
+ txOffset, txKey)
+```
+
+<a id="conditional-formatting"></a>
+
+### Условные операторы и циклы
+
+Оператор `if` не должен разбиваться на строки; многострочные условия `if` могут
+привести к [путанице с отступами](#indentation-confusion).
+
+```go
+// Плохо:
+// Второй оператор if выровнен с кодом внутри блока if, что вызывает
+// путаницу с отступами.
+if db.CurrentStatusIs(db.InTransaction) &&
+ db.ValuesEqual(db.TransactionKey(), row.Key()) {
+ return db.Errorf(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
+}
+```
+
+Если короткое замыкание не требуется, булевы операнды могут быть извлечены
+напрямую:
+
+```go
+// Хорошо:
+inTransaction := db.CurrentStatusIs(db.InTransaction)
+keysMatch := db.ValuesEqual(db.TransactionKey(), row.Key())
+if inTransaction && keysMatch {
+ return db.Error(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
+}
+```
+
+Также могут быть другие локальные переменные, которые можно извлечь, особенно
+если условие уже повторяется:
+
+```go
+// Хорошо:
+uid := user.GetUniqueUserID()
+if db.UserIsAdmin(uid) || db.UserHasPermission(uid, perms.ViewServerConfig) || db.UserHasPermission(uid, perms.CreateGroup) {
+ // ...
+}
+```
+
+```go
+// Плохо:
+if db.UserIsAdmin(user.GetUniqueUserID()) || db.UserHasPermission(user.GetUniqueUserID(), perms.ViewServerConfig) || db.UserHasPermission(user.GetUniqueUserID(), perms.CreateGroup) {
+ // ...
+}
+```
+
+Операторы `if`, содержащие замыкания или многострочные литералы структур, должны
+обеспечить, чтобы [скобки совпадали](#literal-matching-braces), чтобы избежать
+[путаницы с отступами](#indentation-confusion).
+
+```go
+// Хорошо:
+if err := db.RunInTransaction(func(tx *db.TX) error {
+ return tx.Execute(userUpdate, x, y, z)
+}); err != nil {
+ return fmt.Errorf("user update failed: %s", err)
+}
+```
+
+```go
+// Хорошо:
+if _, err := client.Update(ctx, &upb.UserUpdateRequest{
+ ID: userID,
+ User: user,
+}); err != nil {
+ return fmt.Errorf("user update failed: %s", err)
+}
+```
+
+Аналогично, не пытайтесь вставлять искусственные разрывы строк в операторы
+`for`. Вы всегда можете позволить строке быть просто длинной, если нет
+элегантного способа ее рефакторить:
+
+```go
+// Хорошо:
+for i, max := 0, collection.Size(); i < max && !collection.HasPendingWriters(); i++ {
+ // ...
+}
+```
+
+Однако часто он есть:
+
+```go
+// Хорошо:
+for i, max := 0, collection.Size(); i < max; i++ {
+ if collection.HasPendingWriters() {
+ break
+ }
+ // ...
+}
+```
+
+Операторы `switch` и `case` также должны оставаться на одной строке.
+
+```go
+// Хорошо:
+switch good := db.TransactionStatus(); good {
+case db.TransactionStarting, db.TransactionActive, db.TransactionWaiting:
+ // ...
+case db.TransactionCommitted, db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+```go
+// Плохо:
+switch bad := db.TransactionStatus(); bad {
+case db.TransactionStarting,
+ db.TransactionActive,
+ db.TransactionWaiting:
+ // ...
+case db.TransactionCommitted,
+ db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+Если строка чрезмерно длинная, сделайте отступ для всех вариантов (`case`) и
+отделите их пустой строкой, чтобы избежать [путаницы с
+отступами](#indentation-confusion):
+
+```go
+// Хорошо:
+switch db.TransactionStatus() {
+case
+ db.TransactionStarting,
+ db.TransactionActive,
+ db.TransactionWaiting,
+ db.TransactionCommitted:
+
+ // ...
+case db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+В условных выражениях, сравнивающих переменную с константой, помещайте значение
+переменной слева от оператора равенства:
+
+```go
+// Хорошо:
+if result == "foo" {
+ // ...
+}
+```
+
+Вместо менее ясной формулировки, где константа идет первой (["условия в стиле
+Йоды"](https://en.wikipedia.org/wiki/Yoda_conditions)):
+
+```go
+// Плохо:
+if "foo" == result {
+ // ...
+}
+```
+
+<a id="copying"></a>
+
+### Копирование
+
+<a id="TOC-Copying"></a>
+
+Чтобы избежать неожиданного псевдонимного доступа (aliasing) и подобных ошибок,
+будьте осторожны при копировании структуры из другого пакета. Например, объекты
+синхронизации, такие как `sync.Mutex`, не должны копироваться.
+
+Тип `bytes.Buffer` содержит срез `[]byte` и, в качестве оптимизации для
+небольших строк, небольшой массив байтов, на который может ссылаться срез. Если
+вы скопируете `Buffer`, срез в копии может стать псевдонимом массива в
+оригинале, вызывая неожиданные эффекты при последующих вызовах методов.
+
+В целом, не копируйте значение типа `T`, если его методы ассоциированы с
+указательным типом, `*T`.
+
+```go
+// Плохо:
+b1 := bytes.Buffer{}
+b2 := b1
+```
+
+Вызов метода, принимающего получателя по значению, может скрыть копирование.
+Когда вы создаете API, вам обычно следует принимать и возвращать типы-указатели,
+если ваши структуры содержат поля, которые не должны копироваться.
+
+Эти примеры допустимы:
+
+```go
+// Хорошо:
+type Record struct {
+ buf bytes.Buffer
+ // другие поля опущены
+}
+
+func New() *Record {...}
+
+func (r *Record) Process(...) {...}
+
+func Consumer(r *Record) {...}
+```
+
+Но эти обычно ошибочны:
+
+```go
+// Плохо:
+type Record struct {
+ buf bytes.Buffer
+ // другие поля опущены
+}
+
+
+func (r Record) Process(...) {...} // Создает копию r.buf
+
+func Consumer(r Record) {...} // Создает копию r.buf
+```
+
+Эти рекомендации также применимы к копированию `sync.Mutex`.
+
+<a id="dont-panic"></a>
+
+### Не используйте panic
+
+<a id="TOC-Don-t-Panic"></a>
+
+Не используйте `panic` для обычной обработки ошибок. Вместо этого используйте
+`error` и множественные возвращаемые значения. См. [раздел Effective Go об
+ошибках].
+
+В `package main` и коде инициализации рассмотрите [`log.Exit`] для ошибок,
+которые должны завершать программу (например, неверная конфигурация), поскольку
+во многих из этих случаев трассировка стека не поможет читателю. Обратите
+внимание, что [`log.Exit`] вызывает [`os.Exit`] и отложенные (deferred) функции
+не будут выполнены.
+
+Для ошибок, указывающих на "невозможные" условия, а именно на ошибки, которые
+всегда должны быть обнаружены при проверке кода и/или тестировании, функция
+может разумно вернуть ошибку или вызвать [`log.Fatal`].
+
+Также см. [когда panic допустим](https://neonxp.ru/pages/gostyleguide/google/best-practices/.md#when-to-panic).
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+[раздел Effective Go об ошибках]: http://golang.org/doc/effective_go.html#errors
+[`os.Exit`]: https://pkg.go.dev/os#Exit
+
+<a id="must-functions"></a>
+
+### Функции Must
+
+Вспомогательные функции настройки, которые останавливают программу при неудаче,
+следуют соглашению об именовании `MustXYZ` (или `mustXYZ`). Как правило, их
+следует вызывать только на раннем этапе запуска программы, а не для таких вещей,
+как пользовательский ввод, где предпочтительна обычная обработка ошибок Go.
+
+Это часто возникает для функций, вызываемых для инициализации переменных уровня
+пакета исключительно во время [инициализации
+пакета](https://golang.org/ref/spec#Package_initialization) (например,
+[template.Must](https://golang.org/pkg/text/template/#Must) и
+[regexp.MustCompile](https://golang.org/pkg/regexp/#MustCompile)).
+
+```go
+// Хорошо:
+func MustParse(version string) *Version {
+ v, err := Parse(version)
+ if err != nil {
+ panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err))
+ }
+ return v
+}
+
+// "Константа" уровня пакета. Если бы мы хотели использовать `Parse`, нам пришлось бы
+// устанавливать значение в `init`.
+var DefaultVersion = MustParse("1.2.3")
+```
+
+То же соглашение может использоваться во вспомогательных функциях тестирования,
+которые останавливают только текущий тест (используя `t.Fatal`). Такие помощники
+часто удобны при создании тестовых значений, например, в полях структур
+[табличных тестов](#table-driven-tests), поскольку функции, возвращающие ошибки,
+не могут быть напрямую присвоены полю структуры.
+
+```go
+// Хорошо:
+func mustMarshalAny(t *testing.T, m proto.Message) *anypb.Any {
+ t.Helper()
+ any, err := anypb.New(m)
+ if err != nil {
+ t.Fatalf("mustMarshalAny(t, m) = %v; want %v", err, nil)
+ }
+ return any
+}
+
+func TestCreateObject(t *testing.T) {
+ tests := []struct{
+ desc string
+ data *anypb.Any
+ }{
+ {
+ desc: "my test case",
+ // Создание значений непосредственно внутри тестовых случаев табличного теста.
+ data: mustMarshalAny(t, mypb.Object{}),
+ },
+ // ...
+ }
+ // ...
+}
+```
+
+В обоих этих случаях ценность этого шаблона в том, что помощники могут быть
+вызваны в "знаковом" контексте. Этих помощников не следует вызывать в местах,
+где трудно обеспечить перехват ошибки, или в контексте, где ошибка должна быть
+[проверена](#handle-errors) (например, во многих обработчиках запросов). Для
+постоянных входных данных это позволяет тестам легко убедиться, что аргументы
+`Must` корректны, а для непостоянных входных данных это позволяет тестам
+проверять, что ошибки [правильно обработаны или
+распространены](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling).
+
+Где функции `Must` используются в тесте, их обычно следует [помечать как
+тестовый помощник](#mark-test-helpers) и вызывать `t.Fatal` при ошибке (см.
+[обработка ошибок во вспомогательных тестовых
+функциях](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-helper-error-handling) для дополнительных
+соображений по использованию этого).
+
+Их не следует использовать, когда [обычная обработка
+ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling) возможна (включая некоторый рефакторинг):
+
+```go
+// Плохо:
+func Version(o *servicepb.Object) (*version.Version, error) {
+ // Вернуть ошибку вместо использования функций Must.
+ v := version.MustParse(o.GetVersionString())
+ return dealiasVersion(v)
+}
+```
+
+<a id="goroutine-lifetimes"></a>
+
+### Время жизни горутин
+
+<a id="TOC-GoroutineLifetimes"></a>
+
+Когда вы порождаете горутины, сделайте ясным, когда или завершаются ли они.
+
+Горутины могут "подтекать", блокируясь на отправке или получении по каналу.
+Сборщик мусора не завершит горутину, заблокированную на канале, даже если
+никакая другая горутина не имеет ссылки на этот канал.
+
+Даже когда горутины не подтекают, оставлять их активными, когда они больше не
+нужны, может вызвать другие тонкие и трудные для диагностики проблемы. Отправка
+в закрытый канал вызывает панику.
+
+```go
+// Плохо:
+ch := make(chan int)
+ch <- 42
+close(ch)
+ch <- 13 // panic
+```
+
+Изменение все еще используемых входных данных "после того, как результат не
+нужен", может привести к гонкам данных. Оставление горутин активными на
+произвольное время может привести к непредсказуемому использованию памяти.
+
+Параллельный код должен быть написан так, чтобы время жизни горутин было
+очевидным. Обычно это означает сохранение кода, связанного с синхронизацией, в
+пределах области видимости функции и вынесение логики в [синхронные функции].
+Если параллелизм все еще не очевиден, важно задокументировать, когда и почему
+горутины завершаются.
+
+Код, следующий лучшим практикам использования контекста, часто помогает
+прояснить это. Обычно это управляется с помощью [`context.Context`]:
+
+```go
+// Хорошо:
+func (w *Worker) Run(ctx context.Context) error {
+ var wg sync.WaitGroup
+ // ...
+ for item := range w.q {
+ // process возвращается самое позднее при отмене контекста.
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ process(ctx, item)
+ }()
+ }
+ // ...
+ wg.Wait() // Предотвращает переживание порожденными горутинами этой функции.
+}
+```
+
+Существуют другие варианты вышеизложенного, использующие простые сигнальные
+каналы, такие как `chan struct{}`, синхронизированные переменные, [условные
+переменные][rethinking-slides] и многое другое. Важная часть заключается в том,
+что конец горутины очевиден для последующих сопровождающих.
+
+В отличие от этого, следующий код небрежен в отношении того, когда его
+порожденные горутины завершаются:
+
+```go
+// Плохо:
+func (w *Worker) Run() {
+ // ...
+ for item := range w.q {
+ // process возвращается, когда завершается, если вообще завершится, возможно, не обрабатывая корректно
+ // переход состояния или завершение самой программы Go.
+ go process(item)
+ }
+ // ...
+}
+```
+
+Этот код может выглядеть нормально, но у него есть несколько скрытых проблем:
+
+* Код, вероятно, имеет неопределенное поведение в продакшене, и программа
+ может не завершиться чисто, даже если операционная система освободит
+ ресурсы.
+
+* Код трудно осмысленно протестировать из-за неопределенного жизненного цикла.
+
+* Код может подтекать ресурсами, как описано выше.
+
+См. также:
+
+* [Никогда не запускайте горутину, не зная, как она остановится][cheney-stop]
+* Переосмысление классических паттернов параллелизма:
+ [слайды][rethinking-slides], [видео][rethinking-video]
+* [Когда программы на Go завершаются]
+* [Соглашения по документации: Контексты]
+
+[синхронные функции]: #synchronous-functions
+[cheney-stop]:
+ https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop
+[rethinking-slides]:
+ https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view
+[rethinking-video]: https://www.youtube.com/watch?v=5zXAHh5tJqQ
+[Когда программы на Go завершаются]: https://changelog.com/gotime/165
+[Соглашения по документации: Контексты]:
+ best-practices.md#documentation-conventions-contexts
+
+<a id="interfaces"></a>
+
+### Интерфейсы
+
+<a id="TOC-Interfaces"></a>
+
+Интерфейсы Go обычно принадлежат пакету, который *потребляет* значения типа
+интерфейса, а не пакету, который *реализует* тип интерфейса. Реализующий пакет
+должен возвращать конкретные (обычно указатель или структура) типы. Таким
+образом, новые методы могут быть добавлены к реализациям без необходимости
+обширного рефакторинга. Подробнее см. [GoTip #49: Принимайте интерфейсы,
+возвращайте конкретные типы].
+
+Не экспортируйте [тестовый дубль (test double)][double types] реализации
+интерфейса из API, который его потребляет. Вместо этого спроектируйте API так,
+чтобы его можно было тестировать с помощью [публичного API] [реальной
+реализации]. См. [GoTip #42: Создание заглушки для тестирования] для
+подробностей. Даже когда нецелесообразно использовать реальную реализацию, может
+не потребоваться вводить интерфейс, полностью покрывающий все методы в реальном
+типе; потребитель может создать интерфейс, содержащий только нужные ему методы,
+как показано в [GoTip #78: Минимально жизнеспособные интерфейсы].
+
+Для тестирования пакетов, использующих Stubby RPC клиенты, используйте реальное
+клиентское соединение. Если реальный сервер не может быть запущен в тесте,
+внутренняя практика Google — получить реальное клиентское соединение с локальным
+[тестовым дублем] с использованием внутреннего пакета rpctest (скоро будет!).
+
+Не определяйте интерфейсы до того, как они используются (см. [TotT: Code
+Health: Eliminate YAGNI Smells][tott-438] ). Без реалистичного примера
+использования слишком сложно увидеть, нужен ли интерфейс вообще, не говоря уже о
+том, какие методы он должен содержать.
+
+Не используйте параметры типа интерфейса, если пользователям пакета не нужно
+передавать различные типы для них.
+
+Не экспортируйте интерфейсы, которые не нужны пользователям пакета.
+
+**TODO:** Написать более подробный документ об интерфейсах и дать на него ссылку
+здесь.
+
+[GoTip #42: Создание заглушки для тестирования]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #49: Принимайте интерфейсы, возвращайте конкретные типы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #78: Минимально жизнеспособные интерфейсы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[реальной реализации]: best-practices#use-real-transports
+[публичному API]:
+ https://abseil.io/resources/swe-book/html/ch12.html#test_via_public_apis
+[double types]:
+ https://abseil.io/resources/swe-book/html/ch13.html#techniques_for_using_test_doubles
+[тестовым дублем]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+[tott-438]:
+ https://testing.googleblog.com/2017/08/code-health-eliminate-yagni-smells.html
+
+```go
+// Хорошо:
+package consumer // consumer.go
+
+type Thinger interface { Thing() bool }
+
+func Foo(t Thinger) string { ... }
+```
+
+```go
+// Хорошо:
+package consumer // consumer_test.go
+
+type fakeThinger struct{ ... }
+func (t fakeThinger) Thing() bool { ... }
+...
+if Foo(fakeThinger{...}) == "x" { ... }
+```
+
+```go
+// Плохо:
+package producer
+
+type Thinger interface { Thing() bool }
+
+type defaultThinger struct{ ... }
+func (t defaultThinger) Thing() bool { ... }
+
+func NewThinger() Thinger { return defaultThinger{ ... } }
+```
+
+```go
+// Хорошо:
+package producer
+
+type Thinger struct{ ... }
+func (t Thinger) Thing() bool { ... }
+
+func NewThinger() Thinger { return Thinger{ ... } }
+```
+
+<a id="generics"></a>
+
+### Дженерики (Generics)
+
+Дженерики (формально называемые "[Параметрами типа]") разрешены там, где они
+удовлетворяют вашим бизнес-требованиям. Во многих приложениях обычный подход с
+использованием существующих языковых возможностей (срезы, карты, интерфейсы и
+т.д.) работает так же хорошо без добавленной сложности, поэтому будьте осторожны
+с преждевременным использованием. См. обсуждение [наименьшего механизма (least
+mechanism)](https://neonxp.ru/pages/gostyleguide/google/guide/#least-mechanism).
+
+При введении экспортируемого API, использующего дженерики, убедитесь, что он
+должным образом задокументирован. Настоятельно рекомендуется включать
+мотивирующие исполняемые [примеры].
+
+Не используйте дженерики только потому, что вы реализуете алгоритм или структуру
+данных, которой не важен тип элементов. Если на практике инстанцируется только
+один тип, начните с того, чтобы ваш код работал с этим типом без использования
+дженериков вообще. Добавить полиморфизм позже будет проще по сравнению с
+удалением абстракции, которая оказывается ненужной.
+
+Не используйте дженерики для создания предметно-ориентированных языков (DSL). В
+частности, воздержитесь от введения фреймворков обработки ошибок, которые могут
+значительно обременять читателей. Вместо этого предпочитайте установленные
+методы [обработки ошибок](#errors). Для тестирования особенно остерегайтесь
+введения [библиотек утверждений (assertion libraries)](#assert) или фреймворков,
+которые приводят к менее полезным [сообщениям об ошибках
+тестов](#useful-test-failures).
+
+В общем:
+
+* [Пишите код, не проектируйте типы]. Из выступления на GopherCon от Роберта
+ Грисемера и Яна Лэнса Тейлора.
+* Если у вас есть несколько типов, которые разделяют полезный объединяющий
+ интерфейс, рассмотрите моделирование решения с использованием этого
+ интерфейса. Дженерики могут не понадобиться.
+* В противном случае вместо того, чтобы полагаться на тип `any` и чрезмерное
+ [переключение типов (type switching)](https://tour.golang.org/methods/16),
+ рассмотрите дженерики.
+
+См. также:
+
+* [Использование дженериков в Go], выступление Яна Лэнса Тейлора
+
+* [Учебник по дженерикам] на сайте Go
+
+[Учебник по дженерикам]: https://go.dev/doc/tutorial/generics
+[Параметрами типа]: https://go.dev/design/43651-type-parameters
+[Использование дженериков в Go]: https://www.youtube.com/watch?v=nr8EpUO9jhw
+[Пишите код, не проектируйте типы]:
+ https://www.youtube.com/watch?v=Pa_e9EeCdy8&t=1250s
+
+<a id="pass-values"></a>
+
+### Передача по значению
+
+<a id="TOC-PassValues"></a>
+
+Не передавайте указатели в качестве аргументов функций только для экономии
+нескольких байтов. Если функция читает свой аргумент `x` только как `*x` на всем
+протяжении, то аргумент не должен быть указателем. Распространенные примеры
+этого включают передачу указателя на строку (`*string`) или указателя на
+значение интерфейса (`*io.Reader`). В обоих случаях само значение имеет
+фиксированный размер и может быть передано напрямую.
+
+Этот совет не применим к большим структурам или даже малым структурам, которые
+могут увеличиться в размере. В частности, сообщения protocol buffer обычно
+следует обрабатывать по указателю, а не по значению. Тип-указатель удовлетворяет
+интерфейсу `proto.Message` (принимаемому `proto.Marshal`, `protocmp.Transform`,
+и т.д.), а сообщения protocol buffer могут быть довольно большими и часто
+увеличиваются со временем.
+
+<a id="receiver-type"></a>
+
+### Тип получателя (Receiver)
+
+<a id="TOC-ReceiverType"></a>
+
+[Получатель метода] может быть передан либо по значению, либо по указателю, как
+если бы он был обычным параметром функции. Выбор между ними основан на том, в
+составе какого [набора методов] метод должен находиться.
+
+[Получатель метода]: https://golang.org/ref/spec#Method_declarations
+[набора методов]: https://golang.org/ref/spec#Method_sets
+
+**Корректность важнее скорости или простоты.** Бывают случаи, когда вы должны
+использовать значение-указатель. В других случаях выбирайте указатели для
+больших типов или в качестве будущего доказательства, если у вас нет хорошего
+понимания того, как будет расти код, и используйте значения для простых [простых
+старых данных].
+
+Список ниже подробно описывает каждый случай:
+
+* Если получатель является срезом и метод не переслаивает (reslice) и не
+ перераспределяет (reallocate) срез, используйте значение, а не указатель.
+
+ ```go
+ // Хорошо:
+ type Buffer []byte
+
+ func (b Buffer) Len() int { return len(b) }
+ ```
+
+* Если методу нужно мутировать получателя, получатель должен быть указателем.
+
+ ```go
+ // Хорошо:
+ type Counter int
+
+ func (c *Counter) Inc() { *c++ }
+
+ // См. https://pkg.go.dev/container/heap.
+ type Queue []Item
+
+ func (q *Queue) Push(x Item) { *q = append([]Item{x}, *q...) }
+ ```
+
+* Если получатель является структурой, содержащей поля, которые [не могут быть
+ безопасно скопированы](#copying), используйте получатель-указатель.
+ Распространенные примеры — [`sync.Mutex`] и другие типы синхронизации.
+
+ ```go
+ // Хорошо:
+ type Counter struct {
+ mu sync.Mutex
+ total int
+ }
+
+ func (c *Counter) Inc() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.total++
+ }
+ ```
+
+ **Совет:** Проверьте [Godoc] типа для информации о том, безопасно ли или
+ небезопасно его копировать.
+
+* Если получатель является "большой" структурой или массивом,
+ получатель-указатель может быть более эффективным. Передача структуры
+ эквивалентна передаче всех ее полей или элементов в качестве аргументов
+ методу. Если это кажется слишком большим для [передачи по
+ значению](#pass-values), указатель — хороший выбор.
+
+* Для методов, которые будут вызываться или выполняться параллельно с другими
+ функциями, которые изменяют получателя, используйте значение, если эти
+ изменения не должны быть видны вашему методу; в противном случае используйте
+ указатель.
+
+* Если получатель является структурой или массивом, любой элемент которого
+ является указателем на что-то, что может быть изменено, предпочтите
+ получателя-указателя, чтобы сделать намерение изменяемости понятным для
+ читателя.
+
+ ```go
+ // Хорошо:
+ type Counter struct {
+ m *Metric
+ }
+
+ func (c *Counter) Inc() {
+ c.m.Add(1)
+ }
+ ```
+
+* Если получатель является [встроенным типом], таким как целое число или
+ строка, который не нужно изменять, используйте значение.
+
+ ```go
+ // Хорошо:
+ type User string
+
+ func (u User) String() { return string(u) }
+ ```
+
+* Если получатель является картой (map), функцией или каналом, используйте
+ значение, а не указатель.
+
+ ```go
+ // Хорошо:
+ // См. https://pkg.go.dev/net/http#Header.
+ type Header map[string][]string
+
+ func (h Header) Add(key, value string) { /* опущено */ }
+ ```
+
+* Если получатель является "маленьким" массивом или структурой, которые
+ естественным образом являются типом значения без изменяемых полей и без
+ указателей, получатель по значению обычно является правильным выбором.
+
+ ```go
+ // Хорошо:
+ // См. https://pkg.go.dev/time#Time.
+ type Time struct { /* опущено */ }
+
+ func (t Time) Add(d Duration) Time { /* опущено */ }
+ ```
+
+* В случае сомнений используйте получатель-указатель.
+
+В качестве общего руководства предпочитайте, чтобы методы для типа были либо все
+указательными методами, либо всеми методами по значению.
+
+**Примечание:** Существует много дезинформации о том, может ли передача значения
+или указателя в функцию повлиять на производительность. Компилятор может выбрать
+передачу указателей на значения в стеке, а также копирование значений в стеке,
+но эти соображения не должны перевешивать читаемость и корректность кода в
+большинстве обстоятельств. Когда производительность действительно имеет
+значение, важно профилировать оба подхода с реалистичным бенчмарком, прежде чем
+решать, что один подход превосходит другой.
+
+[простых старых данных]: https://en.wikipedia.org/wiki/Passive_data_structure
+[`sync.Mutex`]: https://pkg.go.dev/sync#Mutex
+[встроенным типом]: https://pkg.go.dev/builtin
+
+<a id="switch-break"></a>
+
+### `switch` и `break`
+
+<a id="TOC-SwitchBreak"></a>
+
+Не используйте операторы `break` без целевых меток в конце предложений `switch`;
+они избыточны. В отличие от C и Java, предложения `switch` в Go автоматически
+завершаются, и для достижения поведения в стиле C требуется оператор
+`fallthrough`. Используйте комментарий, а не `break`, если хотите прояснить цель
+пустого предложения.
+
+```go
+// Хорошо:
+switch x {
+case "A", "B":
+ buf.WriteString(x)
+case "C":
+ // обрабатывается вне оператора switch
+default:
+ return fmt.Errorf("unknown value: %q", x)
+}
+```
+
+```go
+// Плохо:
+switch x {
+case "A", "B":
+ buf.WriteString(x)
+ break // этот break избыточен
+case "C":
+ break // этот break избыточен
+default:
+ return fmt.Errorf("unknown value: %q", x)
+}
+```
+
+> **Примечание:** Если предложение `switch` находится внутри цикла `for`,
+> использование `break` внутри `switch` не выходит из охватывающего цикла `for`.
+>
+> ```go
+> for {
+> switch x {
+> case "A":
+> break // выходит из switch, не из цикла
+> }
+> }
+> ```
+>
+> Чтобы выйти из охватывающего цикла, используйте метку на операторе `for`:
+>
+> ```go
+> loop:
+> for {
+> switch x {
+> case "A":
+> break loop // выходит из цикла
+> }
+> }
+> ```
+
+<a id="synchronous-functions"></a>
+
+### Синхронные функции
+
+<a id="TOC-SynchronousFunctions"></a>
+
+Синхронные функции возвращают свои результаты напрямую и завершают любые
+обратные вызовы или операции с каналами перед возвратом. Предпочитайте
+синхронные функции асинхронным.
+
+Синхронные функции сохраняют горутины локализованными внутри вызова. Это
+помогает рассуждать об их времени жизни и избегать утечек и гонок данных.
+Синхронные функции также легче тестировать, поскольку вызывающая сторона может
+передать входные данные и проверить выходные без необходимости опроса или
+синхронизации.
+
+При необходимости вызывающая сторона может добавить параллелизм, вызвав функцию
+в отдельной горутине. Однако довольно сложно (иногда невозможно) убрать ненужный
+параллелизм на стороне вызывающего.
+
+См. также:
+
+* "Переосмысление классических паттернов параллелизма", выступление Брайана
+ Миллса: [слайды][rethinking-slides], [видео][rethinking-video]
+
+<a id="type-aliases"></a>
+
+### Псевдонимы типов (Type aliases)
+
+<a id="TOC-TypeAliases"></a>
+
+Используйте *определение типа*, `type T1 T2`, чтобы определить новый тип.
+Используйте [*псевдоним типа*], `type T1 = T2`, чтобы ссылаться на существующий
+тип без определения нового типа. Псевдонимы типов редки; их основное
+использование — помочь в переносе пакетов в новые места исходного кода. Не
+используйте псевдонимы типов, когда это не нужно.
+
+[*псевдоним типа*]: http://golang.org/ref/spec#Type_declarations
+
+<a id="use-percent-q"></a>
+
+### Используйте %q
+
+<a id="TOC-UsePercentQ"></a>
+
+Функции форматирования Go (`fmt.Printf` и т.д.) имеют глагол `%q`, который
+печатает строки внутри двойных кавычек.
+
+```go
+// Хорошо:
+fmt.Printf("value %q looks like English text", someText)
+```
+
+Предпочитайте использование `%q` вместо эквивалентного ручного способа с
+использованием `%s`:
+
+```go
+// Плохо:
+fmt.Printf("value \"%s\" looks like English text", someText)
+// Также избегайте вручную оборачивать строки одинарными кавычками:
+fmt.Printf("value '%s' looks like English text", someText)
+```
+
+Использование `%q` рекомендуется в выводе, предназначенном для людей, где
+входное значение может быть пустым или содержать управляющие символы. Тихую
+пустую строку заметить очень сложно, но `""` явно выделяется как таковая.
+
+<a id="use-any"></a>
+
+### Используйте any
+
+Go 1.18 вводит тип `any` как [псевдоним] для `interface{}`. Поскольку это
+псевдоним, `any` эквивалентен `interface{}` во многих ситуациях, а в других
+легко взаимозаменяем через явное преобразование. Предпочитайте использовать
+`any` в новом коде.
+
+[псевдоним]:
+ https://go.googlesource.com/proposal/+/master/design/18130-type-alias.md
+
+## Общие библиотеки
+
+<a id="flags"></a>
+
+### Флаги
+
+<a id="TOC-Flags"></a>
+
+Программы Go в кодовой базе Google используют внутренний вариант [стандартного
+пакета `flag`]. Он имеет схожий интерфейс, но хорошо взаимодействует с
+внутренними системами Google. Имена флагов в бинарных файлах Go должны
+предпочитать использование подчеркиваний для разделения слов, хотя переменные,
+хранящие значение флага, должны следовать стандартному стилю именования Go
+([mixed caps]). Конкретно, имя флага должно быть в змеином регистре
+(snake_case), а имя переменной должно быть эквивалентным именем в верблюжьем
+регистре (camelCase).
+
+```go
+// Хорошо:
+var (
+ pollInterval = flag.Duration("poll_interval", time.Minute, "Interval to use for polling.")
+)
+```
+
+```go
+// Плохо:
+var (
+ poll_interval = flag.Int("pollIntervalSeconds", 60, "Interval to use for polling in seconds.")
+)
+```
+
+Флаги должны определяться только в `package main` или эквивалентном.
+
+Универсальные пакеты должны настраиваться с использованием API Go, а не путем
+пробивания до интерфейса командной строки; не позволяйте импорту библиотеки
+экспортировать новые флаги как побочный эффект. То есть предпочитайте явные
+аргументы функций или присваивание полей структуры или, что гораздо реже и под
+самым строгим контролем, экспортируемые глобальные переменные. В крайне редком
+случае, когда необходимо нарушить это правило, имя флага должно четко указывать
+пакет, который он настраивает.
+
+Если ваши флаги являются глобальными переменными, поместите их в свою
+собственную группу `var`, следующую после раздела импортов.
+
+Существуют дополнительные обсуждения о лучших практиках создания [сложных CLI] с
+подкомандами.
+
+См. также:
+
+* [Tip of the Week #45: Avoid Flags, Especially in Library Code][totw-45]
+* [Go Tip #10: Configuration Structs and
+ Flags](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+
+[стандартного пакета `flag`]: https://golang.org/pkg/flag/
+[mixed caps]: guide#mixed-caps
+[сложных CLI]: best-practices#complex-clis
+[totw-45]: https://abseil.io/tips/45
+
+<a id="logging"></a>
+
+### Логирование
+
+Программы Go в кодовой базе Google используют вариант стандартного [`log`]
+пакета. Он имеет схожий, но более мощный интерфейс и хорошо взаимодействует с
+внутренними системами Google. Пакет с открытым исходным кодом этой библиотеки
+доступен как [пакет `glog`], и проекты Google с открытым исходным кодом могут
+использовать его, но в этом руководстве он повсюду упоминается как `log`.
+
+**Примечание:** Для аномальных завершений программы эта библиотека использует
+`log.Fatal` для аварийного завершения с трассировкой стека и `log.Exit` для
+остановки без нее. Здесь нет `log.Panic` функции, как в стандартной библиотеке.
+
+**Совет:** `log.Info(v)` эквивалентно `log.Infof("%v", v)`, и то же самое
+относится к другим уровням логирования. Предпочитайте версию без форматирования,
+когда вам нечего форматировать.
+
+См. также:
+
+* Лучшие практики по [логированию ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-logging) и
+ [пользовательским уровням детализации (verbosity
+ levels)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#vlog)
+* Когда и как использовать пакет log для [остановки
+ программы](https://neonxp.ru/pages/gostyleguide/google/best-practices/#checks-and-panics)
+
+[`log`]: https://pkg.go.dev/log
+[`log/slog`]: https://pkg.go.dev/log/slog
+[пакет `glog`]: https://pkg.go.dev/github.com/golang/glog
+[`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit
+[`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal
+
+<a id="contexts"></a>
+
+### Контексты (Contexts)
+
+<a id="TOC-Contexts"></a>
+
+Значения типа [`context.Context`] несут учетные данные безопасности, информацию
+трассировки, сроки и сигналы отмены через границы API и процессов. В отличие от
+C++ и Java, которые в кодовой базе Google используют локальное хранилище потоков
+(thread-local storage), программы Go передают контексты явно вдоль всей цепочки
+вызовов функций от входящих RPC и HTTP запросов к исходящим запросам.
+
+[`context.Context`]: https://pkg.go.dev/context
+
+При передаче в функцию или метод [`context.Context`] всегда является первым
+параметром.
+
+```go
+func F(ctx context.Context /* другие аргументы */) {}
+```
+
+Исключения:
+
+* В HTTP обработчике, где контекст берется из
+ [`req.Context()`](https://pkg.go.dev/net/http#Request.Context).
+* В потоковых RPC методах, где контекст берется из потока.
+
+ Код, использующий потоковый gRPC, получает контекст из метода `Context()` в
+ сгенерированном типе сервера, который реализует `grpc.ServerStream`. См.
+ [документацию по сгенерированному коду
+ gRPC](https://grpc.io/docs/languages/go/generated-code/).
+
+* В точках входа (entrypoint functions) (см. ниже примеры таких функций)
+ используйте [`context.Background()`] или, для тестов,
+ [`tb.Context()`](https://pkg.go.dev/testing#TB.Context).
+
+ * В бинарных целях: `main`
+ * В общем коде и библиотеках: `init`
+ * В тестах: `TestXXX`, `BenchmarkXXX`, `FuzzXXX`
+
+> **Примечание**: Очень редко код в середине цепочки вызовов требует создания
+> базового контекста самостоятельно с помощью [`context.Background()`]. Всегда
+> предпочитайте брать контекст у вашего вызывающего, если это не неправильный
+> контекст.
+>
+> Вы можете столкнуться с серверными библиотеками (реализация Stubby, gRPC или
+> HTTP во фреймворке сервера Google для Go), которые создают новый объект
+> контекста для каждого запроса. Эти контексты сразу заполняются информацией из
+> входящего запроса, так что при передаче в обработчик запроса прикрепленные
+> значения контекста были распространены к нему через сетевую границу от
+> клиента-вызывающего. Более того, время жизни этих контекстов ограничено
+> временем запроса: когда запрос завершен, контекст отменяется.
+>
+> Если вы не реализуете серверный фреймворк, вы не должны создавать контексты с
+> помощью [`context.Background()`] в библиотечном коде. Вместо этого
+> предпочитайте использование отсоединения контекста (context detachment),
+> которое упоминается ниже, если доступен существующий контекст. Если вы
+> думаете, что вам действительно нужно [`context.Background()`] вне функций
+> точек входа, обсудите это со списком рассылки Google Go style, прежде чем
+> приступать к реализации.
+
+Соглашение о том, что [`context.Context`] идет первым в функциях, также
+применимо к тестовым помощникам.
+
+```go
+// Хорошо:
+func readTestFile(ctx context.Context, t *testing.T, path string) string {}
+```
+
+Не добавляйте поле context в тип структуры. Вместо этого добавьте параметр
+context в каждый метод типа, которому нужно передавать его дальше. Единственное
+исключение — для методов, чья сигнатура должна соответствовать интерфейсу в
+стандартной библиотеке или в сторонней библиотеке вне контроля Google. Такие
+случаи очень редки и должны быть обсуждены со списком рассылки Google Go style
+до реализации и проверки на читаемость.
+
+**Примечание:** Go 1.24 добавил метод [`(testing.TB).Context()`]. В тестах
+предпочитайте использовать [`(testing.TB).Context()`] вместо
+[`context.Background()`] для предоставления начального [`context.Context`],
+используемого тестом. Вспомогательные функции, настройка окружения или тестовых
+дублей и другие функции, вызываемые из тела тестовой функции, которые требуют
+контекст, должны иметь его явно переданным.
+
+[`(testing.TB).Context()`]: https://pkg.go.dev/testing#TB.Context
+
+Код в кодовой базе Google, который должен порождать фоновые операции, способные
+выполняться после отмены родительского контекста, может использовать внутренний
+пакет для отсоединения. Следите за [issue
+#40221](https://github.com/golang/go/issues/40221) для обсуждений об
+альтернативе с открытым исходным кодом.
+
+Поскольку контексты неизменяемы, нормально передавать один и тот же контекст в
+несколько вызовов, которые разделяют один и тот же дедлайн, сигнал отмены,
+учетные данные, родительскую трассировку и т.д.
+
+См. также:
+
+* [Контексты и структуры]
+
+[`context.Background()`]: https://pkg.go.dev/context/#Background
+[Контексты и структуры]: https://go.dev/blog/context-and-structs
+
+<a id="custom-contexts"></a>
+
+#### Пользовательские контексты
+
+Не создавайте пользовательские типы контекстов и не используйте интерфейсы,
+отличные от [`context.Context`], в сигнатурах функций. Для этого правила нет
+исключений.
+
+Представьте, если бы каждая команда имела свой пользовательский контекст. Каждый
+вызов функции из пакета `p` в пакет `q` должен был бы определять, как
+преобразовать `p.Context` в `q.Context`, для всех пар пакетов `p` и `q`. Это
+нецелесообразно и создает ошибки для людей, и это делает автоматизированные
+рефакторинги, добавляющие параметры контекста, почти невозможными.
+
+Если у вас есть данные приложения для передачи, поместите их в параметр, в
+получателе, в глобальных переменных или в значении `Context`, если они
+действительно принадлежат там. Создание вашего собственного типа контекста
+неприемлемо, поскольку оно подрывает способность команды Go заставить программы
+Go правильно работать в продакшене.
+
+<a id="crypto-rand"></a>
+
+### crypto/rand
+
+<a id="TOC-CryptoRand"></a>
+
+Не используйте пакет `math/rand` для генерации ключей, даже одноразовых. Если не
+задано начальное значение (seed), генератор полностью предсказуем. При задании
+начального значения `time.Nanoseconds()` существует всего несколько бит
+энтропии. Вместо этого используйте `Reader` из `crypto/rand`, и если вам нужен
+текст, выводите в шестнадцатеричном или base64.
+
+```go
+// Хорошо:
+import (
+ "crypto/rand"
+ // "encoding/base64"
+ // "encoding/hex"
+ "fmt"
+
+ // ...
+)
+
+func Key() string {
+ buf := make([]byte, 16)
+ if _, err := rand.Read(buf); err != nil {
+ log.Fatalf("Out of randomness, should never happen: %v", err)
+ }
+ return fmt.Sprintf("%x", buf)
+ // или hex.EncodeToString(buf)
+ // или base64.StdEncoding.EncodeToString(buf)
+}
+```
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+<a id="useful-test-failures"></a>
+
+## Полезные сообщения об ошибках тестов
+
+<a id="TOC-UsefulTestFailures"></a>
+
+Должна быть возможность диагностировать неудачу теста без чтения исходного кода
+теста. Тесты должны завершаться неудачей с полезными сообщениями,
+детализирующими:
+
+* Что вызвало неудачу
+* Какие входные данные привели к ошибке
+* Фактический результат
+* Что ожидалось
+
+Конкретные соглашения для достижения этой цели изложены ниже.
+
+<a id="assert"></a>
+
+### Библиотеки утверждений (Assertion libraries)
+
+<a id="TOC-Assert"></a>
+
+Не создавайте "библиотеки утверждений" в качестве помощников для тестирования.
+
+Библиотеки утверждений — это библиотеки, которые пытаются объединить проверку и
+формирование сообщений об ошибках в тесте (хотя те же ловушки могут применяться
+и к другим тестовым помощникам). Подробнее о различии между тестовыми
+помощниками и библиотеками утверждений см. [лучшие
+практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions).
+
+```go
+// Плохо:
+var obj BlogPost
+
+assert.IsNotNil(t, "obj", obj)
+assert.StringEq(t, "obj.Type", obj.Type, "blogPost")
+assert.IntEq(t, "obj.Comments", obj.Comments, 2)
+assert.StringNotEq(t, "obj.Body", obj.Body, "")
+```
+
+Библиотеки утверждений имеют тенденцию либо останавливать тест рано (если
+`assert` вызывает `t.Fatalf` или `panic`), либо опускать соответствующую
+информацию о том, что тест получил правильно:
+
+```go
+// Плохо:
+package assert
+
+func IsNotNil(t *testing.T, name string, val any) {
+ if val == nil {
+ t.Fatalf("Data %s = nil, want not nil", name)
+ }
+}
+
+func StringEq(t *testing.T, name, got, want string) {
+ if got != want {
+ t.Fatalf("Data %s = %q, want %q", name, got, want)
+ }
+}
+```
+
+Сложные функции утверждений часто не предоставляют [полезные сообщения об
+ошибках] и контекст, существующий внутри тестовой функции. Слишком много функций
+утверждений и библиотек приводит к фрагментированному опыту разработчика: какую
+библиотеку утверждений использовать, какой стиль формата вывода она должна
+выдавать и т.д.? Фрагментация создает ненужную путаницу, особенно для
+сопровождающих библиотек и авторов крупномасштабных изменений, которые отвечают
+за исправление потенциальных поломок вниз по течению. Вместо создания
+предметно-ориентированного языка для тестирования используйте сам Go.
+
+Библиотеки утверждений часто выносят сравнения и проверки равенства.
+Предпочитайте использование стандартных библиотек, таких как [`cmp`] и [`fmt`],
+вместо этого:
+
+```go
+// Хорошо:
+var got BlogPost
+
+want := BlogPost{
+ Comments: 2,
+ Body: "Hello, world!",
+}
+
+if !cmp.Equal(got, want) {
+ t.Errorf("Blog post = %v, want = %v", got, want)
+}
+```
+
+Для более предметно-ориентированных помощников сравнения предпочитайте
+возвращать значение или ошибку, которые можно использовать в сообщении об ошибке
+теста, вместо передачи `*testing.T` и вызова его методов сообщения об ошибках:
+
+```go
+// Хорошо:
+func postLength(p BlogPost) int { return len(p.Body) }
+
+func TestBlogPost_VeritableRant(t *testing.T) {
+ post := BlogPost{Body: "I am Gunnery Sergeant Hartman, your senior drill instructor."}
+
+ if got, want := postLength(post), 60; got != want {
+ t.Errorf("Length of post = %v, want %v", got, want)
+ }
+}
+```
+
+**Лучшая практика:** Если бы `postLength` была нетривиальной, было бы разумно
+тестировать ее напрямую, независимо от любых тестов, которые ее используют.
+
+См. также:
+
+* [Сравнение равенства и разницы (diffs)](#types-of-equality)
+* [Печать разниц (diffs)](#print-diffs)
+* Подробнее о различии между тестовыми помощниками и помощниками утверждений
+ см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions)
+* Раздел [Go FAQ] о [фреймворках тестирования] и их мнение об их отсутствии
+
+[полезные сообщения об ошибках]: #useful-test-failures
+[`fmt`]: https://golang.org/pkg/fmt/
+[помеча как тестовый помощник]: #mark-test-helpers
+[Go FAQ]: https://go.dev/doc/faq
+[фреймворках тестирования]: https://go.dev/doc/faq#testing_framework
+
+<a id="identify-the-function"></a>
+
+### Идентификация функции
+
+В большинстве тестов сообщения об ошибках должны включать имя функции, которая
+завершилась неудачей, даже если это кажется очевидным из имени тестовой функции.
+Конкретно, ваше сообщение об ошибке должно быть `YourFunc(%v) = %v, want %v`
+вместо просто `got %v, want %v`.
+
+<a id="identify-the-input"></a>
+
+### Идентификация входных данных
+
+В большинстве тестов сообщения об ошибках должны включать входные данные
+функции, если они кратки. Если соответствующие свойства входных данных не
+очевидны (например, потому что входные данные большие или непрозрачные), вы
+должны называть свои тестовые случаи описанием того, что тестируется, и выводить
+это описание как часть вашего сообщения об ошибке.
+
+<a id="got-before-want"></a>
+
+### Got перед want
+
+Вывод тестов должен включать фактическое значение, которое вернула функция,
+перед печатью значения, которое ожидалось. Стандартный формат для вывода тестов
+это `YourFunc(%v) = %v, want %v`. Там, где вы бы написали "actual" и "expected",
+предпочитайте использовать слова "got" и "want" соответственно.
+
+Для разниц (diffs) направленность менее очевидна, и поэтому важно включить ключ,
+чтобы помочь в интерпретации неудачи. См. [раздел о печати разниц]. Независимо
+от порядка diff, который вы используете в своих сообщениях об ошибках, вы должны
+явно указать его как часть сообщения об ошибке, поскольку существующий код
+непоследователен в порядке.
+
+[раздел о печати разниц]: #print-diffs
+
+<a id="compare-full-structures"></a>
+
+### Полное сравнение структур
+
+Если ваша функция возвращает структуру (или любой тип данных с несколькими
+полями, такой как срезы, массивы и карты), избегайте написания тестового кода,
+который выполняет ручное поле-за-полем сравнение структуры. Вместо этого
+сконструируйте данные, которые вы ожидаете, что ваша функция вернет, и
+сравнивайте напрямую с помощью [глубокого сравнения (deep comparison)].
+
+**Примечание:** Это не применяется, если ваши данные содержат нерелевантные
+поля, которые затемняют намерение теста.
+
+Если вашу структуру нужно сравнивать на приблизительное (или эквивалентное
+семантическое) равенство или она содержит поля, которые не могут быть сравнены
+на равенство (например, если одно из полей — `io.Reader`), настройка сравнения
+[`cmp.Diff`] или [`cmp.Equal`] с опциями [`cmpopts`], такими как
+[`cmpopts.IgnoreInterfaces`], может удовлетворить ваши потребности
+([пример](https://play.golang.org/p/vrCUNVfxsvF)).
+
+Если ваша функция возвращает несколько возвращаемых значений, вам не нужно
+оборачивать их в структуру перед сравнением. Просто сравните возвращаемые
+значения по отдельности и выведите их.
+
+```go
+// Хорошо:
+val, multi, tail, err := strconv.UnquoteChar(`\"Fran & Freddie's Diner\"`, '"')
+if err != nil {
+ t.Fatalf(...)
+}
+if val != `"` {
+ t.Errorf(...)
+}
+if multi {
+ t.Errorf(...)
+}
+if tail != `Fran & Freddie's Diner"` {
+ t.Errorf(...)
+}
+```
+
+[глубокого сравнения (deep comparison)]: #types-of-equality
+[`cmpopts`]: https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts
+[`cmpopts.IgnoreInterfaces`]:
+ https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#IgnoreInterfaces
+
+<a id="compare-stable-results"></a>
+
+### Сравнение стабильных результатов
+
+Избегайте сравнения результатов, которые могут зависеть от стабильности вывода
+пакета, которым вы не владеете. Вместо этого тест должен сравнивать семантически
+релевантную информацию, которая стабильна и устойчива к изменениям в
+зависимостях. Для функциональности, возвращающей форматированную строку или
+сериализованные байты, как правило, небезопасно предполагать, что вывод
+стабилен.
+
+Например, [`json.Marshal`] может измениться (и менялся в прошлом) в конкретных
+байтах, которые он выдает. Тесты, выполняющие строковое равенство на JSON
+строке, могут сломаться, если пакет `json` изменит способ сериализации байтов.
+Вместо этого более надежный тест будет анализировать содержимое JSON строки и
+убеждаться, что оно семантически эквивалентно некоторой ожидаемой структуре
+данных.
+
+[`json.Marshal`]: https://golang.org/pkg/encoding/json/#Marshal
+
+<a id="keep-going"></a>
+
+### Продолжать выполнение
+
+Тесты должны продолжать работать как можно дольше, даже после неудачи, чтобы
+вывести все неудачные проверки за один запуск. Таким образом, разработчик,
+который исправляет падающий тест, не должен перезапускать тест после исправления
+каждой ошибки, чтобы найти следующую ошибку.
+
+Предпочитайте вызов `t.Error` вместо `t.Fatal` для сообщения о несоответствии.
+При сравнении нескольких различных свойств вывода функции используйте `t.Error`
+для каждого из этих сравнений.
+
+```go
+// Хорошо:
+gotMean, gotVariance, err := MyDistribution(input)
+if err != nil {
+ t.Fatalf("MyDistribution(%v) returned unexpected error: %v", input, err)
+}
+if diff := cmp.Diff(wantMean, gotMean); diff != "" {
+ t.Errorf("MyDistribution(%v) returned unexpected difference in mean value (-want +got):\n%s", input, diff)
+}
+if diff := cmp.Diff(wantVariance, gotVariance); diff != "" {
+ t.Errorf("MyDistribution(%v) returned unexpected difference in variance value (-want +got):\n%s", input, diff)
+}
+```
+
+Вызов `t.Fatal` в первую очередь полезен для сообщения о неожиданном условии
+(таком как ошибка или несоответствие вывода), когда последующие неудачи были бы
+бессмысленны или даже вводили бы исследователя в заблуждение. Обратите внимание,
+как код ниже вызывает `t.Fatalf` и *затем* `t.Errorf`:
+
+```go
+// Хорошо:
+gotEncoded := Encode(input)
+if gotEncoded != wantEncoded {
+ t.Fatalf("Encode(%q) = %q, want %q", input, gotEncoded, wantEncoded)
+ // Не имеет смысла декодировать из неожиданного закодированного ввода.
+}
+gotDecoded, err := Decode(gotEncoded)
+if err != nil {
+ t.Fatalf("Decode(%q) returned unexpected error: %v", gotEncoded, err)
+}
+if gotDecoded != input {
+ t.Errorf("Decode(%q) = %q, want %q", gotEncoded, gotDecoded, input)
+}
+```
+
+Для табличных тестов рассмотрите использование подтестов и используйте `t.Fatal`
+вместо `t.Error` и `continue`. См. также [GoTip #25: Subtests: Making Your Tests
+Lean](https://google.github.io/styleguide/go/index.html#gotip).
+
+**Лучшая практика:** Для дальнейшего обсуждения о том, когда следует
+использовать `t.Fatal`, см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#t-fatal).
+
+<a id="types-of-equality"></a>
+
+### Сравнение равенства и разницы (diffs)
+
+Оператор `==` вычисляет равенство, используя [определенные языком сравнения].
+Скалярные значения (числа, булевы и т.д.) сравниваются на основе их значений, но
+только некоторые структуры и интерфейсы можно сравнивать таким образом.
+Указатели сравниваются на основе того, указывают ли они на одну и ту же
+переменную, а не на основе равенства значений, на которые они указывают.
+
+Пакет [`cmp`] может сравнивать более сложные структуры данных, не обрабатываемые
+адекватно `==`, такие как срезы. Используйте [`cmp.Equal`] для сравнения
+равенства и [`cmp.Diff`] для получения читаемой человеком разницы между
+объектами.
+
+```go
+// Хорошо:
+want := &Doc{
+ Type: "blogPost",
+ Comments: 2,
+ Body: "This is the post body.",
+ Authors: []string{"isaac", "albert", "emmy"},
+}
+if !cmp.Equal(got, want) {
+ t.Errorf("AddPost() = %+v, want %+v", got, want)
+}
+```
+
+Как универсальная библиотека сравнения, `cmp` может не знать, как сравнивать
+определенные типы. Например, она может сравнивать сообщения protobuf только если
+передана опция [`protocmp.Transform`].
+
+<!-- The order of want and got here is deliberate. See comment in #print-diffs. -->
+
+```go
+// Хорошо:
+if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
+ t.Errorf("Foo() returned unexpected difference in protobuf messages (-want +got):\n%s", diff)
+}
+```
+
+Хотя пакет `cmp` не является частью стандартной библиотеки Go, он поддерживается
+командой Go и должен обеспечивать стабильные результаты равенства с течением
+времени. Он настраивается пользователем и должен удовлетворять большинству
+потребностей в сравнении.
+
+[определенные языком сравнения]: http://golang.org/ref/spec#Comparison_operators
+[`cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[`cmp.Equal`]: https://pkg.go.dev/github.com/google/go-cmp/cmp#Equal
+[`cmp.Diff`]: https://pkg.go.dev/github.com/google/go-cmp/cmp#Diff
+[`protocmp.Transform`]:
+ https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp#Transform
+
+Существующий код может использовать следующие более старые библиотеки и может
+продолжать использовать их для согласованности:
+
+* [`pretty`] создает эстетически приятные отчеты о различиях. Однако он
+ довольно намеренно считает значения, имеющие одинаковое визуальное
+ представление, равными. В частности, `pretty` не улавливает различия между
+ nil срезами и пустыми, не чувствителен к разным реализациям интерфейсов с
+ идентичными полями, и возможно использовать вложенную карту в качестве
+ основы для сравнения со значением структуры. Он также сериализует все
+ значение в строку перед созданием diff, и, как таковой, не является хорошим
+ выбором для сравнения больших значений. По умолчанию он сравнивает
+ неэкспортируемые поля, что делает его чувствительным к изменениям в деталях
+ реализации в ваших зависимостях. По этой причине нецелесообразно
+ использовать `pretty` на сообщениях protobuf.
+
+[`pretty`]: https://pkg.go.dev/github.com/kylelemons/godebug/pretty
+
+Предпочитайте использовать `cmp` для нового кода, и стоит рассмотреть обновление
+старого кода для использования `cmp`, где и когда это практично.
+
+Старый код может использовать функцию стандартной библиотеки `reflect.DeepEqual`
+для сравнения сложных структур. `reflect.DeepEqual` не следует использовать для
+проверки равенства, так как она чувствительна к изменениям в неэкспортированных
+полях и другим деталям реализации. Код, использующий `reflect.DeepEqual`, должен
+быть обновлен до одной из вышеупомянутых библиотек.
+
+**Примечание:** Пакет `cmp` предназначен для тестирования, а не для
+использования в продакшене. Как таковой, он может вызывать панику, когда
+подозревает, что сравнение выполнено неправильно, чтобы дать инструкции
+пользователям, как улучшить тест, чтобы он был менее хрупким. Учитывая
+склонность cmp к панике, он непригоден для кода, который используется в
+продакшене, поскольку ложная паника может быть фатальной.
+
+<a id="level-of-detail"></a>
+
+### Уровень детализации
+
+Обычное сообщение об ошибке, подходящее для большинства тестов Go, это
+`YourFunc(%v) = %v, want %v`. Однако бывают случаи, которые могут потребовать
+больше или меньше деталей:
+
+* Тесты, выполняющие сложные взаимодействия, должны также описывать
+ взаимодействия. Например, если один и тот же `YourFunc` вызывается
+ несколько раз, идентифицируйте, какой вызов провалил тест. Если важно знать
+ любое дополнительное состояние системы, включите это в вывод ошибки (или
+ хотя бы в логи).
+* Если данные представляют собой сложную структуру со значительным шаблонным
+ кодом, допустимо описать только важные части в сообщении, но не чрезмерно
+ затемняйте данные.
+* Ошибки настройки не требуют такого же уровня детализации. Если тестовый
+ помощник заполняет таблицу Spanner, но Spanner был выключен, вам, вероятно,
+ не нужно включать, какие тестовые входные данные вы собирались сохранить в
+ базе данных. `t.Fatalf("Setup: Failed to set up test database: %s", err)`
+ обычно достаточно полезно, чтобы решить проблему.
+
+**Совет:** Заставьте ваш режим отказа срабатывать во время разработки.
+Просмотрите, как выглядит сообщение об ошибке, и может ли сопровождающий
+эффективно справиться с неудачей.
+
+Существуют некоторые методы для ясного воспроизведения тестовых входов и
+выходов:
+
+* При выводе строковых данных [`%q` часто полезен](#use-percent-q) для
+ выделения важности значения и более легкого обнаружения плохих значений.
+* При выводе (маленьких) структур `%+v` может быть полезнее, чем `%v`.
+* Когда проверка больших значений завершается неудачей, [печать разницы
+ (diff)](#print-diffs) может облегчить понимание неудачи.
+
+<a id="print-diffs"></a>
+
+### Печать разниц (diffs)
+
+Если ваша функция возвращает большой вывод, читающему может быть трудно найти
+различия, когда ваш тест проваливается. Вместо печати и возвращенного значения,
+и ожидаемого значения создайте diff.
+
+Для вычисления разниц для таких значений предпочтительна `cmp.Diff`, особенно
+для новых тестов и нового кода, но могут использоваться и другие инструменты.
+См. [типы равенства] для рекомендаций относительно сильных и слабых сторон
+каждой функции.
+
+* [`cmp.Diff`]
+
+* [`pretty.Compare`]
+
+Вы можете использовать пакет [`diff`] для сравнения многострочных строк или
+списков строк. Вы можете использовать это как строительный блок для других видов
+разниц.
+
+[типы равенства]: #types-of-equality
+[`diff`]: https://pkg.go.dev/github.com/kylelemons/godebug/diff
+[`pretty.Compare`]:
+ https://pkg.go.dev/github.com/kylelemons/godebug/pretty#Compare
+
+Добавьте некоторый текст в ваше сообщение об ошибке, объясняющий направление
+diff.
+
+<!--
+The reversed order of want and got in these examples is intentional, as this is
+the prevailing order across the Google codebase. The lack of a stance on which
+order to use is also intentional, as there is no consensus which is
+"most readable."
+
+
+-->
+
+* Что-то вроде `diff (-want +got)` хорошо, когда вы используете пакеты `cmp`,
+ `pretty` и `diff` (если вы передаете `(want, got)` в функцию), потому что
+ `-` и `+`, которые вы добавляете в строку формата, будут соответствовать `-`
+ и `+`, которые фактически появляются в начале строк diff. Если вы передаете
+ `(got, want)` в вашу функцию, правильным ключом будет `(-got +want)` вместо
+ этого.
+
+* Пакет `messagediff` использует другой формат вывода, поэтому сообщение `diff
+ (want -> got)` уместно, когда вы его используете (если вы передаете `(want,
+ got)` в функцию), потому что направление стрелки будет соответствовать
+ направлению стрелки в строках "modified".
+
+Diff будет занимать несколько строк, поэтому вы должны напечатать новую строку
+перед печатью diff.
+
+<a id="test-error-semantics"></a>
+
+### Тестирование семантики ошибок
+
+Когда модульный тест выполняет строковые сравнения или использует обычный `cmp`
+для проверки того, что возвращаются определенные типы ошибок для определенных
+входных данных, вы можете обнаружить, что ваши тесты становятся хрупкими, если
+какое-либо из этих сообщений об ошибках будет переформулировано в будущем.
+Поскольку это может превратить ваш модульный тест в детектор изменений (см.
+[TotT: Change-Detector Tests Considered Harmful][tott-350] ), не используйте
+строковое сравнение для проверки того, какой тип ошибки возвращает ваша функция.
+Однако допустимо использовать строковые сравнения для проверки того, что
+сообщения об ошибках, исходящие из тестируемого пакета, удовлетворяют
+определенным свойствам, например, что оно включает имя параметра.
+
+Значения ошибок в Go обычно имеют компонент, предназначенный для человеческого
+восприятия, и компонент, предназначенный для семантического управления потоком.
+Тесты должны стремиться тестировать только семантическую информацию, которую
+можно надежно наблюдать, а не отображаемую информацию, предназначенную для
+отладки человеком, поскольку последняя часто подвержена будущим изменениям.
+Рекомендации по созданию ошибок со смысловым значением см. [лучшие практики
+относительно ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling). Если ошибка с недостаточной
+семантической информацией исходит из зависимости вне вашего контроля,
+рассмотрите возможность создания отчета об ошибке (bug) владельцу, чтобы помочь
+улучшить API, а не полагаться на разбор сообщения об ошибке.
+
+В рамках модульных тестов часто важно только то, произошла ли ошибка или нет.
+Если да, то достаточно проверить только, была ли ошибка ненулевой, когда вы
+ожидали ошибки. Если вы хотите проверить, что ошибка семантически соответствует
+какой-то другой ошибке, рассмотрите использование [`errors.Is`] или `cmp` с
+[`cmpopts.EquateErrors`].
+
+> **Примечание:** Если тест использует [`cmpopts.EquateErrors`], но все его
+> значения `wantErr` либо `nil`, либо `cmpopts.AnyError`, то использование `cmp`
+> является [ненужным механизмом](https://neonxp.ru/pages/gostyleguide/google/guide/#least-mechanism). Упростите код, сделав
+> поле `want` типа `bool`. Затем вы можете использовать простое сравнение с
+> `!=`.
+>
+> ```go
+> // Хорошо:
+> err := f(test.input)
+> if gotErr := err != nil; gotErr != test.wantErr {
+> t.Errorf("f(%q) = %v, want error presence = %v", test.input, err, test.wantErr)
+> }
+> ```
+
+См. также [GoTip #13: Designing Errors for
+Checking](https://google.github.io/styleguide/go/index.html#gotip).
+
+[tott-350]:
+ https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html
+[`cmpopts.EquateErrors`]:
+ https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#EquateErrors
+[`errors.Is`]: https://pkg.go.dev/errors#Is
+
+<a id="test-structure"></a>
+
+## Структура тестов
+
+<a id="subtests"></a>
+
+### Подтесты (Subtests)
+
+Стандартная библиотека тестирования Go предоставляет возможность [определять
+подтесты]. Это позволяет гибкость в настройке и очистке, управлении
+параллелизмом и фильтрации тестов. Подтесты могут быть полезны (особенно для
+табличных тестов), но их использование не обязательно. См. также [пост в блоге
+Go о подтестах](https://blog.golang.org/subtests).
+
+Подтесты не должны зависеть от выполнения других случаев для успеха или
+начального состояния, поскольку ожидается, что подтесты могут быть запущены
+индивидуально с использованием флагов `go test -run` или выражений [фильтра
+тестов] Bazel.
+
+[определять подтесты]:
+ https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks
+[фильтра тестов]: https://bazel.build/docs/user-manual#test-filter
+
+<a id="subtest-names"></a>
+
+#### Имена подтестов
+
+Назовите ваш подтест так, чтобы он был читаем в выводе теста и полезен в
+командной строке для пользователей фильтрации тестов. Когда вы используете
+`t.Run` для создания подтеста, первый аргумент используется как описательное имя
+для теста. Чтобы гарантировать, что результаты тестов читаемы для людей,
+читающих логи, выбирайте имена подтестов, которые останутся полезными и
+читаемыми после экранирования. Думайте об именах подтестов скорее как об
+идентификаторе функции, чем о прозаическом описании.
+
+Тестовый раннер заменяет пробелы подчеркиваниями и экранирует непечатаемые
+символы. Чтобы обеспечить точную корреляцию между логами тестов и исходным
+кодом, рекомендуется избегать использования этих символов в именах подтестов.
+
+Если ваши тестовые данные выигрывают от более длинного описания, рассмотрите
+возможность поместить описание в отдельное поле (возможно, для печати с помощью
+`t.Log` или рядом с сообщениями об ошибках).
+
+Подтесты могут быть запущены индивидуально с использованием флагов [Go test
+runner] или Bazel [фильтра тестов], поэтому выбирайте описательные имена,
+которые также легко набирать.
+
+> **Предупреждение:** Символы слеша особенно недружелюбны в именах подтестов,
+> поскольку они имеют [особое значение для фильтров тестов].
+>
+> > ```sh
+> > # Плохо:
+> > # Предполагая TestTime и t.Run("America/New_York", ...)
+> > bazel test :mytest --test_filter="Time/New_York" # Ничего не запускает!
+> > bazel test :mytest --test_filter="Time//New_York" # Правильно, но неудобно.
+> > ```
+
+Чтобы [идентифицировать входные данные] функции, включите их в сообщения об
+ошибках теста, где они не будут экранированы тестовым раннером.
+
+```go
+// Хорошо:
+func TestTranslate(t *testing.T) {
+ data := []struct {
+ name, desc, srcLang, dstLang, srcText, wantDstText string
+ }{
+ {
+ name: "hu=en_bug-1234",
+ desc: "regression test following bug 1234. contact: cleese",
+ srcLang: "hu",
+ srcText: "cigarettát és egy öngyújtót kérek",
+ dstLang: "en",
+ wantDstText: "cigarettes and a lighter please",
+ }, // ...
+ }
+ for _, d := range data {
+ t.Run(d.name, func(t *testing.T) {
+ got := Translate(d.srcLang, d.dstLang, d.srcText)
+ if got != d.wantDstText {
+ t.Errorf("%s\nTranslate(%q, %q, %q) = %q, want %q",
+ d.desc, d.srcLang, d.dstLang, d.srcText, got, d.wantDstText)
+ }
+ })
+ }
+}
+```
+
+Вот несколько примеров того, чего следует избегать:
+
+```go
+// Плохо:
+// Слишком многословно.
+t.Run("check that there is no mention of scratched records or hovercrafts", ...)
+// Слеши вызывают проблемы в командной строке.
+t.Run("AM/PM confusion", ...)
+```
+
+См. также [Go Tip #117: Subtest
+Names](https://google.github.io/styleguide/go/index.html#gotip).
+
+[Go test runner]: https://golang.org/cmd/go/#hdr-Testing_flags
+[идентифицировать входные данные]: #identify-the-input
+[особое значение для фильтров тестов]:
+ https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests
+
+<a id="table-driven-tests"></a>
+
+### Табличные тесты
+
+Используйте табличные тесты, когда множество различных тестовых случаев могут
+быть протестированы с помощью сходной тестовой логики.
+
+* При тестировании того, равен ли фактический вывод функции ожидаемому выводу.
+ Например, множество [тестов `fmt.Sprintf`] или минимальный фрагмент ниже.
+* При тестировании того, соответствуют ли выводы функции всегда одному и тому
+ же набору инвариантов. Например, [тесты для `net.Dial`].
+
+[тестов `fmt.Sprintf`]:
+ https://cs.opensource.google/go/go/+/master:src/fmt/fmt_test.go
+[тестов для `net.Dial`]:
+ https://cs.opensource.google/go/go/+/master:src/net/dial_test.go;l=318;drc=5b606a9d2b7649532fe25794fa6b99bd24e7697c
+
+Вот минимальная структура табличного теста. При необходимости вы можете
+использовать другие имена или добавить дополнительные средства, такие как
+подтесты или функции настройки и очистки. Всегда помните о [полезных сообщениях
+об ошибках тестов](#useful-test-failures).
+
+```go
+// Хорошо:
+func TestCompare(t *testing.T) {
+ compareTests := []struct {
+ a, b string
+ want int
+ }{
+ {"", "", 0},
+ {"a", "", 1},
+ {"", "a", -1},
+ {"abc", "abc", 0},
+ {"ab", "abc", -1},
+ {"abc", "ab", 1},
+ {"x", "ab", 1},
+ {"ab", "x", -1},
+ {"x", "a", 1},
+ {"b", "x", -1},
+ // тестирование chunked-реализации runtime·memeq
+ {"abcdefgh", "abcdefgh", 0},
+ {"abcdefghi", "abcdefghi", 0},
+ {"abcdefghi", "abcdefghj", -1},
+ }
+
+ for _, test := range compareTests {
+ got := Compare(test.a, test.b)
+ if got != test.want {
+ t.Errorf("Compare(%q, %q) = %v, want %v", test.a, test.b, got, test.want)
+ }
+ }
+}
+```
+
+**Примечание**: Сообщения об ошибках в приведенном выше примере соответствуют
+рекомендациям [идентифицировать функцию](#identify-the-function) и
+[идентифицировать входные данные](#identify-the-input). Нет необходимости
+[идентифицировать строку численно](#table-tests-identifying-the-row).
+
+Когда некоторые тестовые случаи нужно проверять с помощью логики, отличной от
+других тестовых случаев, уместно написать несколько тестовых функций, как
+объясняется в [GoTip #50: Disjoint Table Tests].
+
+Когда дополнительные тестовые случаи просты (например, базовая проверка ошибок)
+и не вводят условный поток кода в теле цикла табличного теста, допустимо
+включить этот случай в существующий тест, хотя будьте осторожны, используя такую
+логику. То, что начинается просто сегодня, может органически вырасти во что-то
+неподдерживаемое.
+
+Например:
+
+```go
+func TestDivide(t *testing.T) {
+ tests := []struct {
+ dividend, divisor int
+ want int
+ wantErr bool
+ }{
+ {
+ dividend: 4,
+ divisor: 2,
+ want: 2,
+ },
+ {
+ dividend: 10,
+ divisor: 2,
+ want: 5,
+ },
+ {
+ dividend: 1,
+ divisor: 0,
+ wantErr: true,
+ },
+ }
+
+ for _, test := range tests {
+ got, err := Divide(test.dividend, test.divisor)
+ if (err != nil) != test.wantErr {
+ t.Errorf("Divide(%d, %d) error = %v, want error presence = %t", test.dividend, test.divisor, err, test.wantErr)
+ }
+
+ // В этом примере мы тестируем значение результата только когда тестируемая функция не завершилась неудачей.
+ if err != nil {
+ continue
+ }
+
+ if got != test.want {
+ t.Errorf("Divide(%d, %d) = %d, want %d", test.dividend, test.divisor, got, test.want)
+ }
+ }
+}
+```
+
+Более сложная логика в вашем тестовом коде, например сложная проверка ошибок на
+основе условных различий в настройке теста (часто основанных на входных
+параметрах табличного теста), может быть [трудной для
+понимания](https://neonxp.ru/pages/gostyleguide/google/guide/#maintainability), когда каждая запись в таблице имеет
+специализированную логику на основе входных данных. Если тестовые случаи имеют
+разную логику, но идентичную настройку, последовательность
+[подтестов](#subtests) внутри одной тестовой функции может быть более читаемой.
+Тестовый помощник также может быть полезен для упрощения настройки теста с целью
+сохранения читаемости тела теста.
+
+Вы можете комбинировать табличные тесты с несколькими тестовыми функциями.
+Например, при тестировании того, что вывод функции точно соответствует
+ожидаемому выводу и что функция возвращает ненулевую ошибку для неверного ввода,
+лучшим подходом является написание двух отдельных табличных тестовых функций:
+одну для обычных не-ошибочных выводов, и одну для ошибочных выводов.
+
+[GoTip #50: Disjoint Table Tests]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="table-tests-data-driven"></a>
+
+#### Тестовые случаи на основе данных
+
+Строки табличных тестов иногда могут становиться сложными, причем значения строк
+диктуют условное поведение внутри тестового случая. Дополнительная ясность от
+дублирования между тестовыми случаями необходима для читаемости.
+
+```go
+// Хорошо:
+type decodeCase struct {
+ name string
+ input string
+ output string
+ err error
+}
+
+func TestDecode(t *testing.T) {
+ // setupCodex медленный, так как создает реальный Codex для теста.
+ codex := setupCodex(t)
+
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %v, want %v", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+
+func TestDecodeWithFake(t *testing.T) {
+ // fakeCodex — это быстрое приближение реального Codex.
+ codex := newFakeCodex()
+
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %v, want %v", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+```
+
+В контринте ниже обратите внимание, как сложно различить, какой тип `Codex`
+используется на тестовый случай в настройке случая. (Выделенные части нарушают
+совет из [TotT: Data Driven Traps!][tott-97] .)
+
+```go
+// Плохо:
+type decodeCase struct {
+ name string
+ input string
+ codex testCodex
+ output string
+ err error
+}
+
+type testCodex int
+
+const (
+ fake testCodex = iota
+ prod
+)
+
+func TestDecode(t *testing.T) {
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := tests {
+ t.Run(test.name, func(t *testing.T) {
+ var codex Codex
+ switch test.codex {
+ case fake:
+ codex = newFakeCodex()
+ case prod:
+ codex = setupCodex(t)
+ default:
+ t.Fatalf("Unknown codex type: %v", codex)
+ }
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %q, want %q", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+```
+
+[tott-97]: https://testing.googleblog.com/2008/09/tott-data-driven-traps.html
+
+<a id="table-tests-identifying-the-row"></a>
+
+#### Идентификация строки
+
+Не используйте индекс теста в тестовой таблице в качестве замены именования
+ваших тестов или печати входных данных. Никто не хочет проходить через вашу
+тестовую таблицу и считать записи, чтобы выяснить, какой тестовый случай
+провалился.
+
+```go
+// Плохо:
+tests := []struct {
+ input, want string
+}{
+ {"hello", "HELLO"},
+ {"wORld", "WORLD"},
+}
+for i, d := range tests {
+ if strings.ToUpper(d.input) != d.want {
+ t.Errorf("Failed on case #%d", i)
+ }
+}
+```
+
+Добавьте описание теста в вашу тестовую структуру и печатайте его вместе с
+сообщениями об ошибках. При использовании подтестов имя вашего подтеста должно
+эффективно идентифицировать строку.
+
+**Важно:** Хотя `t.Run` ограничивает область вывода и выполнения, вы должны
+всегда [идентифицировать входные данные]. Имена строк табличных тестов должны
+следовать [руководству по именованию подтестов].
+
+[идентифицировать входные данные]: #identify-the-input
+[руководству по именованию подтестов]: #subtest-names
+
+<a id="mark-test-helpers"></a>
+
+### Тестовые помощники (Test helpers)
+
+Тестовый помощник — это функция, выполняющая задачу настройки или очистки. Все
+ошибки, возникающие в тестовых помощниках, должны быть ошибками окружения (а не
+кода под тестом) — например, когда тестовая база данных не может быть запущена
+потому что на этой машине не осталось свободных портов.
+
+Если вы передаете `*testing.T`, вызовите [`t.Helper`], чтобы приписать ошибки в
+тестовом помощнике строке, где помощник вызывается. Этот параметр должен идти
+после параметра [контекста](#contexts), если он присутствует, и перед любыми
+оставшимися параметрами.
+
+```go
+// Хорошо:
+func TestSomeFunction(t *testing.T) {
+ golden := readFile(t, "testdata/golden-result.txt")
+ // ... тесты против golden ...
+}
+
+// readFile возвращает содержимое файла данных.
+// Может вызываться только из той же горутины, которая начала тест.
+func readFile(t *testing.T, filename string) string {
+ t.Helper()
+ contents, err := runfiles.ReadFile(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return string(contents)
+}
+```
+
+Не используйте этот шаблон, когда он скрывает связь между неудачей теста и
+условиями, которые к ней привели. Конкретно, рекомендации о [библиотеках
+утверждений](#assert) все еще применяются, и [`t.Helper`] не должен
+использоваться для реализации таких библиотек.
+
+**Совет:** Подробнее о различии между тестовыми помощниками и помощниками
+утверждений см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions).
+
+Хотя вышесказанное относится к `*testing.T`, большая часть рекомендаций остается
+той же для вспомогательных функций бенчмарков и фаззинга.
+
+[`t.Helper`]: https://pkg.go.dev/testing#T.Helper
+
+<a id="test-package"></a>
+
+### Тестовый пакет
+
+<a id="TOC-TestPackage"></a>
+
+<a id="test-same-package"></a>
+
+#### Тесты в том же пакете
+
+Тесты могут быть определены в том же пакете, что и тестируемый код.
+
+Чтобы написать тест в том же пакете:
+
+* Поместите тесты в файл `foo_test.go`
+* Используйте `package foo` для тестового файла
+* Не импортируйте явно тестируемый пакет
+
+```build
+# Хорошо:
+go_library(
+ name = "foo",
+ srcs = ["foo.go"],
+ deps = [
+ ...
+ ],
+)
+
+go_test(
+ name = "foo_test",
+ size = "small",
+ srcs = ["foo_test.go"],
+ library = ":foo",
+ deps = [
+ ...
+ ],
+)
+```
+
+Тест в том же пакете может обращаться к неэкспортированным идентификаторам в
+пакете. Это может обеспечить лучшее покрытие тестами и более лаконичные тесты.
+Имейте в виду, что любые [примеры], объявленные в тесте, не будут иметь имен
+пакетов, которые понадобятся пользователю в их коде.
+
+[`library`]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_library
+[примеры]: #examples
+
+<a id="test-different-package"></a>
+
+#### Тесты в другом пакете
+
+Не всегда уместно или даже возможно определить тест в том же пакете, что и
+тестируемый код. В этих случаях используйте имя пакета с суффиксом `_test`. Это
+исключение из правила "без подчеркиваний" для [имен пакетов](#package-names).
+Например:
+
+* Если интеграционный тест не имеет очевидной библиотеки, к которой он
+ принадлежит
+
+ ```go
+ // Хорошо:
+ package gmailintegration_test
+
+ import "testing"
+ ```
+
+* Если определение тестов в том же пакете приводит к циклическим зависимостям
+
+ ```go
+ // Хорошо:
+ package fireworks_test
+
+ import (
+ "fireworks"
+ "fireworkstestutil" // fireworkstestutil также импортирует fireworks
+ )
+ ```
+
+<a id="use-package-testing"></a>
+
+### Использование пакета `testing`
+
+Стандартная библиотека Go предоставляет пакет [`testing`]. Это единственный
+фреймворк тестирования, разрешенный для кода Go в кодовой базе Google. В
+частности, [библиотеки утверждений](#assert) и сторонние фреймворки тестирования
+не разрешены.
+
+Пакет `testing` предоставляет минимальный, но полный набор функциональности для
+написания хороших тестов:
+
+* Тесты верхнего уровня
+* Бенчмарки
+* [Исполняемые примеры](https://blog.golang.org/examples)
+* Подтесты
+* Логирование
+* Ошибки и фатальные ошибки
+
+Они разработаны для слаженной работы с основными языковыми возможностями, такими
+как [составные литералы] и [if с инициализатором], чтобы позволить авторам
+тестов писать [понятные, читаемые и поддерживаемые тесты].
+
+[`testing` package]: https://pkg.go.dev/testing
+[составные литералы]: https://go.dev/ref/spec#Composite_literals
+[if с инициализатором]: https://go.dev/ref/spec#If_statements
+
+<a id="non-decisions"></a>
+
+## Не-решения
+
+Руководство по стилю не может перечислять позитивные предписания по всем
+вопросам, как не может оно перечислить все вопросы, по которым оно не дает
+мнения. Тем не менее, вот несколько вещей, по которым сообщество читаемости
+ранее спорило и не достигло консенсуса.
+
+* **Локальная инициализация переменных нулевым значением**. `var i int` и `i
+ := 0` эквивалентны. См. также [лучшие практики инициализации].
+* **Пустой составной литерал vs. `new` или `make`**. `&File{}` и `new(File)`
+ эквивалентны. Так же `map[string]bool{}` и `make(map[string]bool)`. См.
+ также [лучшие практики составных объявлений].
+* **Порядок аргументов got, want в вызовах cmp.Diff**. Будьте локально
+ последовательны и [включите легенду](#print-diffs) в ваше сообщение об
+ ошибке.
+* **`errors.New` vs `fmt.Errorf` на неформатированных строках**.
+ `errors.New("foo")` и `fmt.Errorf("foo")` могут использоваться
+ взаимозаменяемо.
+
+Если возникнут особые обстоятельства, наставник по читаемости может сделать
+необязательный комментарий, но в целом автор волен выбирать предпочитаемый им
+стиль в данной ситуации.
+
+Естественно, если что-то, не охваченное руководством по стилю, требует
+дополнительного обсуждения, авторы могут спросить — либо в конкретном ревью,
+либо на внутренних досках сообщений.
+
+[лучшие практики составных объявлений]:
+ https://google.github.io/styleguide/go/best-practices#vardeclcomposite
+[лучшие практики инициализации]:
+ https://google.github.io/styleguide/go/best-practices#vardeclinitialization
diff --git a/content/pages/gostyleguide/google/guide.md b/content/pages/gostyleguide/google/guide.md
new file mode 100644
index 0000000..31b3b33
--- /dev/null
+++ b/content/pages/gostyleguide/google/guide.md
@@ -0,0 +1,495 @@
+---
+order: 3
+title: Google Go Style Guide — Руководство
+---
+
+# Руководство по стилю Go (Go Style Guide)
+
+Оригинал: https://google.github.io/styleguide/go/guide
+
+[Обзор](https://neonxp.ru/pages/gostyleguide/google/) | [Руководство](https://neonxp.ru/pages/gostyleguide/google/guide) | [Решения](https://neonxp.ru/pages/gostyleguide/google/decisions) |
+[Лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices)
+
+
+**Примечание:** Это часть серии документов, описывающих [Стиль Go (Go
+Style)](https://neonxp.ru/pages/gostyleguide/google/) в Google. Данный документ является **[нормативным
+(normative)](https://neonxp.ru/pages/gostyleguide/google/#normative) и [каноническим (canonical)](https://neonxp.ru/pages/gostyleguide/google/#canonical)**.
+Подробнее см. [в обзоре](https://neonxp.ru/pages/gostyleguide/google/#about).
+
+<a id="principles"></a>
+
+## Принципы стиля
+
+Существует несколько основополагающих принципов, которые суммируют подход к
+написанию читаемого кода на Go. Ниже перечислены атрибуты читаемого кода в
+порядке важности:
+
+1. **[Понятность (Clarity)]**: Цель и обоснование кода ясны читателю.
+1. **[Простота (Simplicity)]**: Код достигает своей цели наиболее простым
+ способом.
+1. **[Лаконичность (Concision)]**: Код имеет высокое отношение сигнала к шуму.
+1. **[Поддерживаемость (Maintainability)]**: Код написан так, чтобы его было
+ легко поддерживать.
+1. **[Согласованность (Consistency)]**: Код согласуется с более широкой
+ кодобазой Google.
+
+[Понятность (Clarity)]: #clarity
+[Простота (Simplicity)]: #simplicity
+[Лаконичность (Concision)]: #concision
+[Поддерживаемость (Maintainability)]: #maintainability
+[Согласованность (Consistency)]: #consistency
+
+<a id="clarity"></a>
+
+### Понятность (Clarity)
+
+Основная цель удобочитаемости — создание кода, который понятен читателю.
+
+Понятность достигается в первую очередь за счет эффективного именования,
+полезных комментариев и продуманной организации кода.
+
+Понятность следует рассматривать с точки зрения читателя, а не автора кода.
+Важнее, чтобы код был легок для чтения, а не для написания. Понятность кода
+имеет два аспекта:
+
+* [Что именно делает код?](#clarity-purpose)
+* [Почему код делает то, что он делает?](#clarity-rationale)
+
+<a id="clarity-purpose"></a>
+
+#### Что именно делает код?
+
+Go разработан таким образом, чтобы относительно легко было понять, что делает
+код. В случаях неопределенности или когда читателю могут потребоваться
+предварительные знания для понимания кода, стоит потратить время, чтобы сделать
+цель кода более ясной для будущих читателей. Например, может помочь:
+
+* Использование более описательных имен переменных
+* Добавление дополнительных комментариев
+* Разделение кода пробелами и комментариями
+* Рефакторинг кода в отдельные функции/методы для повышения модульности
+
+Здесь нет универсального решения, но при разработке кода на Go важно отдавать
+приоритет понятности.
+
+<a id="clarity-rationale"></a>
+
+#### Почему код делает то, что он делает?
+
+Обоснование кода часто достаточно ясно передается именами переменных, функций,
+методов или пакетов. Если это не так, важно добавить комментарии. "Почему?"
+особенно важно, когда код содержит нюансы, с которыми читатель может быть не
+знаком, например:
+
+* Нюанс языка, например, замыкание захватит переменную цикла, но само
+ замыкание находится далеко от него
+* Нюанс бизнес-логики, например, проверка прав доступа, которая должна
+ различать реального пользователя и того, кто выдает себя за пользователя
+
+API может требовать осторожного использования. Например, фрагмент кода может
+быть сложным и трудным для понимания из-за соображений производительности, или
+сложная последовательность математических операций может использовать
+преобразования типов неожиданным образом. В этих и многих других случаях важно,
+чтобы сопровождающие комментарии и документация объясняли эти аспекты, чтобы
+будущие сопровождающие не допустили ошибку и чтобы читатели могли понять код без
+необходимости его обратной разработки.
+
+Также важно осознавать, что некоторые попытки повысить понятность (например,
+добавление лишних комментариев) могут фактически затуманить цель кода, добавляя
+беспорядок, пересказывая то, что код уже говорит, противореча коду или добавляя
+нагрузку по поддержке актуальности комментариев. Позвольте коду говорить самому
+за себя (например, делая имена символов самодокументированными), а не добавляйте
+избыточные комментарии. Зачастую лучше, чтобы комментарии объясняли, *почему*
+что-то сделано, а не *что* делает код.
+
+Кодовая база Google в значительной степени единообразна и согласована. Часто
+бывает, что код, который выделяется (например, использованием незнакомого
+шаблона), делает это по уважительной причине, обычно для производительности.
+Поддержание этого свойства важно, чтобы дать читателям понять, куда им следует
+направить внимание при чтении нового фрагмента кода.
+
+Стандартная библиотека содержит множество примеров реализации этого принципа.
+Среди них:
+
+* Комментарии сопровождающих в [`package
+ sort`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/sort.go).
+* Хорошие [запускаемые примеры в том же
+ пакете](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/example_search_test.go),
+ которые полезны как пользователям (они [отображаются в
+ godoc](https://pkg.go.dev/sort#pkg-examples)), так и сопровождающим (они
+ [запускаются как часть тестов](https://neonxp.ru/pages/gostyleguide/google/decisions/#examples)).
+* [`strings.Cut`](https://pkg.go.dev/strings#Cut) — это всего четыре строки
+ кода, но они повышают [понятность и корректность мест вызова
+ (callsites)](https://github.com/golang/go/issues/46336).
+
+<a id="simplicity"></a>
+
+### Простота (Simplicity)
+
+Ваш код на Go должен быть простым для тех, кто его использует, читает и
+поддерживает.
+
+Код на Go должен быть написан наиболее простым способом, достигающим его целей,
+как с точки зрения поведения, так и производительности. Внутри кодовой базы Go в
+Google простой код:
+
+* Легко читается сверху вниз
+* Не предполагает, что вы уже знаете, что он делает
+* Не предполагает, что вы можете запомнить весь предшествующий код
+* Не имеет ненужных уровней абстракции
+* Не имеет имен, которые привлекают внимание к чему-то обыденному
+* Делает передачу значений и принятие решений понятными для читателя
+* Имеет комментарии, которые объясняют *почему*, а не *что* делает код, чтобы
+ избежать будущих отклонений
+* Имеет самодостаточную документацию
+* Имеет полезные ошибки и полезные сообщения об ошибках в тестах
+* Часто может быть взаимоисключающим с "умным" кодом
+
+Могут возникать компромиссы между простотой кода и простотой использования API.
+Например, может иметь смысл сделать код более сложным, чтобы конечному
+пользователю API было легче правильно его вызывать. И наоборот, также может быть
+целесообразно оставить немного дополнительной работы конечному пользователю API,
+чтобы код оставался простым и легким для понимания.
+
+Когда коду требуется сложность, она должна добавляться обдуманно. Обычно это
+необходимо, если требуется дополнительная производительность или если у
+конкретной библиотеки или сервиса есть несколько различных клиентов. Сложность
+может быть оправдана, но она должна сопровождаться документацией, чтобы клиенты
+и будущие сопровождающие могли понять и ориентироваться в ней. Это должно
+дополняться тестами и примерами, демонстрирующими ее правильное использование,
+особенно если существует как "простой", так и "сложный" способ использования
+кода.
+
+Этот принцип не означает, что сложный код нельзя или не следует писать на Go,
+или что коду на Go не разрешено быть сложным. Мы стремимся к кодовой базе,
+которая избегает ненужной сложности, чтобы, когда сложность появляется, это
+указывало на то, что рассматриваемый код требует внимания для понимания и
+поддержки. В идеале должны быть сопроводительные комментарии, объясняющие
+обоснование и указывающие на меры предосторожности. Это часто возникает при
+оптимизации кода для производительности; это часто требует более сложного
+подхода, например, предварительного выделения буфера и его повторного
+использования в течение времени жизни горутины. Когда сопровождающий видит это,
+это должно быть сигналом, что рассматриваемый код критичен для
+производительности, и это должно влиять на осторожность при внесении будущих
+изменений. С другой стороны, если эта сложность применена без необходимости, она
+становится обузой для тех, кому нужно читать или изменять код в будущем.
+
+Если код оказывается очень сложным, в то время как его цель должна быть простой,
+это часто сигнал пересмотреть реализацию, чтобы увидеть, есть ли более простой
+способ достичь того же.
+
+<a id="least-mechanism"></a>
+
+#### Наименьшая механизация (Least mechanism)
+
+Если есть несколько способов выразить одну и ту же идею, предпочтите тот,
+который использует наиболее стандартные инструменты. Сложные механизмы часто
+существуют, но не должны применяться без причины. Легко добавить сложность в код
+по мере необходимости, тогда как гораздо сложнее удалить существующую сложность
+после того, как выяснилось, что она не нужна.
+
+1. Стремитесь использовать базовую конструкцию языка (например, канал, срез
+ (slice), мапу (map), цикл или структуру (struct)), если этого достаточно для
+ вашего случая использования.
+2. Если такой нет, ищите инструмент в стандартной библиотеке (например,
+ HTTP-клиент или механизм шаблонов (template engine)).
+3. Наконец, рассмотрите, есть ли в кодовой базе Google основная библиотека,
+ которой достаточно, прежде чем вводить новую зависимость или создавать свою
+ собственную.
+
+В качестве примера рассмотрим production-код, содержащий флаг, привязанный к
+переменной со значением по умолчанию, которое должно быть переопределено в
+тестах. Если не предполагается тестирование самого интерфейса командной строки
+программы (скажем, с помощью `os/exec`), проще и поэтому предпочтительнее
+переопределить привязанное значение напрямую, а не с помощью `flag.Set`.
+
+Аналогично, если фрагменту кода требуется проверка принадлежности к множеству
+(set membership check), часто достаточно мапы с булевыми значениями (например,
+`map[string]bool`). Библиотеки, предоставляющие типы и функциональность, похожие
+на множества (set), следует использовать только в том случае, если требуются
+более сложные операции, которые невозможны или чрезмерно сложны с мапой.
+
+<a id="concision"></a>
+
+### Лаконичность (Concision)
+
+Лаконичный код на Go имеет высокое отношение сигнала к шуму. Легко различить
+соответствующие детали, а именование и структура направляют читателя через эти
+детали.
+
+Многое может помешать выделению наиболее важных деталей в любой момент:
+
+* Повторяющийся код
+* Лишний синтаксис
+* [Непонятные имена](#naming)
+* Ненужная абстракция
+* Пробелы
+
+Повторяющийся код особенно затмевает различия между каждым почти идентичным
+разделом и требует от читателя визуального сравнения похожих строк кода, чтобы
+найти изменения. [Табличное тестирование (Table-driven testing)] — хороший
+пример механизма, который может лаконично вынести общий код за рамки важных
+деталей каждого повторения, но выбор того, какие части включить в таблицу,
+повлияет на то, насколько легко таблицу понять.
+
+При рассмотрении нескольких способов структурирования кода стоит подумать, какой
+способ делает важные детали наиболее очевидными.
+
+Понимание и использование общих конструкций кода и идиом также важны для
+поддержания высокого отношения сигнала к шуму. Например, следующий блок кода
+очень распространен при [обработке ошибок (error handling)], и читатель может
+быстро понять его назначение.
+
+```go
+// Хорошо:
+if err := doSomething(); err != nil {
+ // ...
+}
+```
+
+Если код выглядит очень похоже, но имеет тонкое отличие, читатель может не
+заметить изменение. В таких случаях стоит намеренно ["усилить" сигнал] проверки
+ошибки, добавив комментарий, чтобы привлечь к нему внимание.
+
+```go
+// Хорошо:
+if err := doSomething(); err == nil { // если ошибки НЕТ
+ // ...
+}
+```
+
+[Табличное тестирование (Table-driven testing)]:
+ https://github.com/golang/go/wiki/TableDrivenTests
+[обработке ошибок (error handling)]: https://go.dev/blog/errors-are-values
+["усилить" сигнал]: best-practices#signal-boost
+
+<a id="maintainability"></a>
+
+### Поддерживаемость (Maintainability)
+
+Код редактируется гораздо чаще, чем пишется. Читаемый код не только понятен
+читателю, который пытается понять, как он работает, но и программисту, которому
+нужно его изменить. Ключевое значение имеет понятность.
+
+Поддерживаемый код:
+
+* Легко модифицируется будущим программистом правильно
+* Имеет API, структурированные таким образом, что они могут элегантно
+ развиваться
+* Четко указывает предположения, которые он делает, и выбирает абстракции,
+ которые соответствуют структуре проблемы, а не структуре кода
+* Избегает ненужной связности и не включает неиспользуемые функции
+* Имеет комплексный набор тестов, чтобы гарантировать сохранение заявленного
+ поведения и корректность важной логики, а тесты предоставляют четкие,
+ действенные диагностические сообщения в случае неудачи
+
+При использовании абстракций, таких как интерфейсы и типы, которые по
+определению удаляют информацию из контекста, в котором они используются, важно
+обеспечить, чтобы они приносили достаточную пользу. Редакторы и IDE могут
+напрямую подключаться к определению метода и показывать соответствующую
+документацию при использовании конкретного типа, но в противном случае могут
+ссылаться только на определение интерфейса. Интерфейсы — мощный инструмент, но
+они имеют свою цену, поскольку сопровождающему, возможно, потребуется понять
+особенности базовой реализации, чтобы правильно использовать интерфейс, что
+должно быть объяснено в документации интерфейса или в месте вызова (call-site).
+
+Поддерживаемый код также избегает скрытия важных деталей в местах, которые легко
+упустить из виду. Например, в каждой из следующих строк кода наличие или
+отсутствие одного символа имеет критическое значение для понимания строки:
+
+```go
+// Плохо:
+// Использование = вместо := может полностью изменить эту строку.
+if user, err = db.UserByID(userID); err != nil {
+ // ...
+}
+```
+
+```go
+// Плохо:
+// Символ ! в середине этой строки очень легко пропустить.
+leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
+```
+
+Ни один из этих примеров не является неверным, но оба могут быть написаны в
+более явной форме или могут иметь сопровождающий комментарий, привлекающий
+внимание к важному поведению:
+
+```go
+// Хорошо:
+u, err := db.UserByID(userID)
+if err != nil {
+ return fmt.Errorf("invalid origin user: %s", err)
+}
+user = u
+```
+
+```go
+// Хорошо:
+// Григорианские високосные годы — это не просто year%4 == 0.
+// См. https://en.wikipedia.org/wiki/Leap_year#Algorithm.
+var (
+ leap4 = year%4 == 0
+ leap100 = year%100 == 0
+ leap400 = year%400 == 0
+)
+leap := leap4 && (!leap100 || leap400)
+```
+
+Таким же образом вспомогательная функция, скрывающая критическую логику или
+важный крайний случай (edge-case), может облегчить ситуации, когда будущее
+изменение не учитывает их должным образом.
+
+Предсказуемые имена — еще одна особенность поддерживаемого кода. Пользователь
+пакета или сопровождающий фрагмента кода должны иметь возможность предсказать
+имя переменной, метода или функции в данном контексте. Параметры функций и имена
+получателей (receiver) для идентичных концепций обычно должны иметь одно и то же
+имя, как для понятности документации, так и для облегчения рефакторинга кода с
+минимальными затратами.
+
+Поддерживаемый код минимизирует свои зависимости (как явные, так и неявные).
+Зависимость от меньшего количества пакетов означает меньше строк кода, которые
+могут повлиять на поведение. Избегание зависимостей от внутреннего или
+недокументированного поведения делает код менее обременительным для поддержки
+при изменении этого поведения в будущем.
+
+При обдумывании того, как структурировать или писать код, стоит потратить время
+на размышления о том, как код может развиваться с течением времени. Если данный
+подход способствует более легким и безопасным будущим изменениям, это часто
+является хорошим компромиссом, даже если это означает несколько более сложный
+дизайн.
+
+<a id="consistency"></a>
+
+### Согласованность (Consistency)
+
+Согласованный код — это код, который выглядит, ощущается и ведет себя так же,
+как и аналогичный код во всей кодовой базе, в контексте команды или пакета и
+даже в пределах одного файла.
+
+Соображения согласованности не отменяют ни один из вышеперечисленных принципов,
+но если необходимо принять решение, часто полезно решить его в пользу
+согласованности.
+
+Согласованность внутри пакета часто является наиболее важным уровнем
+согласованности. Может быть очень неприятно, если одна и та же проблема решается
+несколькими способами в пределах пакета или если одна концепция имеет много имен
+в пределах файла. Однако даже это не должно перевешивать задокументированные
+принципы стиля или глобальную согласованность.
+
+<a id="core"></a>
+
+## Основные рекомендации (Core guidelines)
+
+Эти рекомендации собирают наиболее важные аспекты стиля Go, которым, как
+ожидается, следует весь код на Go. Мы ожидаем, что эти принципы будут изучены и
+соблюдены к моменту получения права на ревью кода (readability). Они не должны
+часто меняться, и новые дополнения должны преодолеть высокий барьер.
+
+Приведенные ниже рекомендации расширяют рекомендации из [Effective Go], которые
+обеспечивают общую основу для кода на Go во всем сообществе.
+
+[Effective Go]: https://go.dev/doc/effective_go
+
+<a id="formatting"></a>
+
+### Форматирование (Formatting)
+
+Все исходные файлы Go должны соответствовать формату, выводимому инструментом
+`gofmt`. Этот формат обеспечивается проверкой перед отправкой (presubmit check)
+в кодовой базе Google. [Сгенерированный код] также обычно должен
+форматироваться (например, с использованием [`format.Source`]), так как он также
+просматривается в Code Search.
+
+[Сгенерированный код]:
+ https://docs.bazel.build/versions/main/be/general.html#genrule
+[`format.Source`]: https://pkg.go.dev/go/format#Source
+
+<a id="mixed-caps"></a>
+
+### MixedCaps
+
+Исходный код Go использует `MixedCaps` или `mixedCaps` (верблюжий регистр, camel
+case) вместо подчеркиваний (змеиный регистр, snake case) при написании составных
+имен.
+
+Это применимо даже тогда, когда это нарушает соглашения в других языках.
+Например, константа называется `MaxLength` (не `MAX_LENGTH`), если она
+экспортируемая (exported), и `maxLength` (не `max_length`), если
+неэкспортируемая (unexported).
+
+Локальные переменные считаются [неэкспортируемыми (unexported)] для целей выбора
+начального регистра.
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+[неэкспортируемыми (unexported)]: https://go.dev/ref/spec#Exported_identifiers
+
+<a id="line-length"></a>
+
+### Длина строки (Line length)
+
+Не существует фиксированной длины строки для исходного кода Go. Если строка
+кажется слишком длинной, предпочтительнее рефакторинг, чем ее разделение. Если
+строка уже настолько короткая, насколько это практично, ей следует позволить
+оставаться длинной.
+
+Не разделяйте строку:
+
+* Перед [изменением отступа (indentation
+ change)](https://neonxp.ru/pages/gostyleguide/google/decisions/#indentation-confusion) (например, объявление функции,
+ условие)
+* Чтобы длинная строка (например, URL) поместилась в несколько более коротких
+ строк
+
+<a id="naming"></a>
+
+### Именование (Naming)
+
+Именование — это скорее искусство, чем наука. В Go имена, как правило, несколько
+короче, чем во многих других языках, но применяются те же [общие принципы].
+Имена должны:
+
+* Не казаться [избыточными (repetitive)](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetition) при
+ использовании
+* Учитывать контекст
+* Не повторять концепции, которые уже ясны
+
+Более конкретные рекомендации по именованию можно найти в [решениях
+(https://neonxp.ru/pages/gostyleguide/google/decisions/)](https://neonxp.ru/pages/gostyleguide/google/decisions/#naming).
+
+[общие принципы]:
+ https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html
+
+<a id="local-consistency"></a>
+
+### Локальная согласованность (Local consistency)
+
+Если в руководстве по стилю ничего не сказано по конкретному вопросу стиля,
+авторы могут свободно выбирать предпочтительный стиль, если только код в
+непосредственной близости (обычно в том же файле или пакете, но иногда в
+пределах каталога команды или проекта) не занял последовательную позицию по
+этому вопросу.
+
+Примеры **допустимых** соображений локального стиля:
+
+* Использование `%s` или `%v` для форматированного вывода ошибок
+* Использование буферизованных каналов вместо мьютексов
+
+Примеры **недопустимых** соображений локального стиля:
+
+* Ограничения на длину строк для кода
+* Использование библиотек тестирования на основе утверждений (assertion-based)
+
+Если локальный стиль противоречит руководству по стилю, но влияние на читаемость
+ограничено одним файлом, это обычно будет отмечено в ревью кода, для которого
+согласованное исправление выходит за рамки данного CL (change list). В этом
+случае уместно завести задачу (bug) для отслеживания исправления.
+
+Если изменение ухудшит существующее отклонение от стиля, выставит его в большем
+количестве API, увеличит количество файлов, в которых присутствует отклонение,
+или внесет фактическую ошибку, то локальная согласованность больше не является
+допустимым обоснованием для нарушения руководства по стилю в новом коде. В этих
+случаях автору уместно либо очистить существующую кодовую базу в том же CL, либо
+выполнить рефакторинг перед текущим CL, либо найти альтернативу, которая, по
+крайней мере, не усугубляет локальную проблему. \ No newline at end of file
diff --git a/content/pages/gostyleguide/google/main.md b/content/pages/gostyleguide/google/main.md
new file mode 100644
index 0000000..b3714ee
--- /dev/null
+++ b/content/pages/gostyleguide/google/main.md
@@ -0,0 +1,184 @@
+---
+order: 1
+title: Google Go Style Guide — Руководство
+---
+
+## О руководстве
+
+<!--more -->
+
+Руководство по стилю Go и сопутствующие документы кодифицируют современные
+наилучшие подходы к написанию читаемого и идиоматичного кода на Go. Следование
+Руководству по стилю не является абсолютным требованием, и эти документы никогда
+не будут исчерпывающими. Наша цель — минимизировать неопределённость при
+написании читаемого кода на Go, чтобы новички в языке могли избежать
+распространённых ошибок. Руководство по стилю также служит для унификации
+рекомендаций по стилю, даваемых любым рецензентом кода Go в Google.
+
+| Документ | Ссылка | Основная аудитория | [Нормативный] | [Канонический] |
+| ------------------------ | ----------------------------------------------------- | ------------------------ | ------------- | -------------- |
+| **Руководство по стилю** | https://google.github.io/styleguide/go/guide | Все | Да | Да |
+| **Решения по стилю** | https://google.github.io/styleguide/go/decisions | Наставники по читаемости | Да | Нет |
+| **Лучшие практики** | https://google.github.io/styleguide/go/best-practices | Все заинтересованные | Нет | Нет |
+
+[Нормативный]: #нормативный
+[Канонический]: #канонический
+
+<a id="docs"></a>
+
+### Документы
+
+1. **[Руководство по стилю](https://google.github.io/styleguide/go/guide)**
+ описывает основы стиля Go в Google. Этот документ является окончательным и
+ служит основой для рекомендаций в «Решениях по стилю» и «Лучших практиках».
+
+1. **[Решения по стилю](https://google.github.io/styleguide/go/decisions)** —
+ это более подробный документ, который суммирует решения по конкретным
+ вопросам стиля и, где уместно, обсуждает обоснование этих решений.
+
+ Эти решения могут иногда меняться на основе новых данных, новых возможностей
+ языка, новых библиотек или возникающих паттернов, но не ожидается, что
+ отдельные программисты Go в Google должны следить за актуальностью этого
+ документа.
+
+1. **[Лучшие практики](https://google.github.io/styleguide/go/best-practices)**
+ документируют некоторые паттерны, которые развивались со временем для
+ решения общих задач, хорошо читаются и устойчивы к потребностям поддержки
+ кода.
+
+ Эти лучшие практики не являются каноническими, но программистам Go в Google
+ рекомендуется использовать их там, где это возможно, для сохранения
+ единообразия и согласованности кодовой базы.
+
+Эти документы призваны:
+
+- Согласовать набор принципов для оценки альтернативных стилей
+- Кодифицировать устоявшиеся вопросы стиля Go
+- Документировать и предоставить канонические примеры идиом Go
+- Документировать плюсы и минусы различных решений по стилю
+- Помочь минимизировать неожиданности при рецензировании читаемости кода Go
+- Помочь наставникам по читаемости использовать согласованную терминологию и
+ рекомендации
+
+Эти документы **не** призваны:
+
+- Быть исчерпывающим списком замечаний, которые можно дать при рецензировании
+ читаемости
+- Перечислять все правила, которые каждый должен помнить и всегда соблюдать
+- Заменять здравый смысл при использовании возможностей языка и стиля
+- Оправдывать масштабные изменения для устранения различий в стиле
+
+Всегда будут существовать различия между разными программистами Go и между
+кодовыми базами разных команд. Однако в интересах Google и Alphabet, чтобы наша
+кодовая база была как можно более согласованной. (Подробнее о согласованности
+см. [руководство](https://neonxp.ru/pages/gostyleguide/google/guide/#consistency)). В связи с этим не стесняйтесь вносить
+улучшения стиля по мере необходимости, но вам не нужно придираться к каждому
+нарушению Руководства по стилю, которое вы обнаружите. В частности, эти
+документы могут меняться со временем, и это не повод вызывать лишнюю суету в
+существующих кодовых базах; достаточно писать новый код, используя новейшие
+лучшие практики, и со временем устранять проблемы поблизости.
+
+Важно понимать, что вопросы стиля по своей природе субъективны и всегда
+сопряжены с компромиссами. Большая часть рекомендаций в этих документах
+субъективна, но, как и в случае с `gofmt`, в обеспечиваемом ими единообразии
+есть значительная ценность. Поэтому рекомендации по стилю не будут меняться без
+должного обсуждения, и программистам Go в Google рекомендуется следовать
+руководству по стилю, даже если они с чем-то не согласны.
+
+<a id="definitions"></a>
+
+## Определения
+
+Ниже приведены определения следующих слов, которые используются во всех
+документах по стилю:
+
+- **Канонический**: Устанавливает предписывающие и долговечные правила <a
+ id="canonical"></a>
+
+ В этих документах «канонический» используется для описания чего-либо, что
+ считается стандартом, которому должен следовать весь код (старый и новый) и
+ который не должен существенно меняться с течением времени. Принципы в
+ канонических документах должны быть понятны как авторам, так и рецензентам,
+ поэтому всё, что включается в канонический документ, должно соответствовать
+ высоким стандартам. Как таковые, канонические документы обычно короче и
+ предписывают меньше элементов стиля, чем неканонические документы.
+
+ https://google.github.io/styleguide/go#canonical
+
+- **Нормативный**: Призван установить согласованность <a id="normative"></a>
+
+ В этих документах «нормативный» используется для описания чего-либо, что
+ является согласованным элементом стиля для использования рецензентами кода
+ Go, чтобы предложения, терминология и обоснования были последовательными.
+ Эти элементы могут меняться со временем, и эти документы будут отражать
+ такие изменения, чтобы рецензенты могли оставаться последовательными и в
+ курсе событий. От авторов кода на Go не ожидается знакомства с нормативными
+ документами, но рецензенты будут часто использовать их в качестве
+ справочного материала при проверке читаемости.
+
+ https://google.github.io/styleguide/go#normative
+
+- **Идиоматичный**: Распространённый и знакомый <a id="idiomatic"></a>
+
+ В этих документах «идиоматичный» используется для обозначения чего-либо, что
+ широко распространено в коде на Go и стало знакомым паттерном, который легко
+ узнать. В целом, идиоматичный паттерн следует предпочитать неидиоматичному,
+ если оба служат одной цели в контексте, поскольку именно это будет наиболее
+ знакомо читателям.
+
+ https://google.github.io/styleguide/go#idiomatic
+
+<a id="references"></a>
+
+## Дополнительные ссылки
+
+Данное руководство предполагает, что читатель знаком с [Effective Go], поскольку
+оно обеспечивает общую основу для кода на Go во всём сообществе Go.
+
+Ниже приведены некоторые дополнительные ресурсы для тех, кто хочет
+самостоятельно изучить стиль Go, и для рецензентов, желающих предоставить в
+своих отзывах дополнительный контекст с ссылками. От участников процесса
+проверки читаемости Go не ожидается знакомства с этими ресурсами, но они могут
+упоминаться в качестве контекста при таких проверках.
+
+[Effective Go]: https://go.dev/doc/effective_go
+
+**Внешние ссылки**
+
+- [Спецификация языка Go](https://go.dev/ref/spec)
+- [Часто задаваемые вопросы по Go](https://go.dev/doc/faq)
+- [Модель памяти Go](https://go.dev/ref/mem)
+- [Структуры данных в Go](https://research.swtch.com/godata)
+- [Интерфейсы в Go](https://research.swtch.com/interfaces)
+- [Поговорки Go](https://go-proverbs.github.io/)
+
+- <a id="gotip"></a> Выпуски Go Tip — следите за обновлениями.
+
+- <a id="unit-testing-practices"></a> Практики модульного тестирования —
+ следите за обновлениями.
+
+**Соответствующие статьи Testing-on-the-Toilet**
+
+- [TotT: Именование идентификаторов][tott-431]
+- [TotT: Тестирование состояния vs. Тестирование взаимодействий][tott-281]
+- [TotT: Эффективное тестирование][tott-324]
+- [TotT: Тестирование, основанное на рисках][tott-329]
+- [TotT: Детекторные тесты считаются вредными][tott-350]
+
+[tott-431]: https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html
+[tott-281]: https://testing.googleblog.com/2013/03/testing-on-toilet-testing-state-vs.html
+[tott-324]: https://testing.googleblog.com/2014/05/testing-on-toilet-effective-testing.html
+[tott-329]: https://testing.googleblog.com/2014/05/testing-on-toilet-risk-driven-testing.html
+[tott-350]: https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html
+
+**Дополнительные внешние материалы**
+
+- [Go и догма](https://research.swtch.com/dogma)
+- [Меньше — значит экспоненциально
+ больше](https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html)
+- [Воображение
+ Эсмеральды](https://commandcenter.blogspot.com/2011/12/esmereldas-imagination.html)
+- [Регулярные выражения для синтаксического
+ анализа](https://commandcenter.blogspot.com/2011/08/regular-expressions-in-lexing-and.html)
+- [Стиль Gofmt никому не нравится, но Gofmt нравится
+ всем](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=8m43s) (YouTube)
diff --git a/content/pages/gostyleguide/uber/index.md b/content/pages/gostyleguide/uber/index.md
new file mode 100644
index 0000000..a8e74fd
--- /dev/null
+++ b/content/pages/gostyleguide/uber/index.md
@@ -0,0 +1,4140 @@
+---
+order: 10
+title: Uber Go Style Guide
+---
+
+# Руководство по стилю Uber для Go
+
+Оригинал: https://github.com/uber-go/guide/blob/master/style.md
+
+<!--more-->
+
+- [Введение](#введение)
+- [Рекомендации](#рекомендации)
+ - [Указатели на интерфейсы](#указатели-на-интерфейсы)
+ - [Проверка соответствия интерфейсу](#проверка-соответствия-интерфейсу)
+ - [Получатели и интерфейсы](#получатели-и-интерфейсы)
+ - [Нулевые значения мьютексов
+ допустимы](#нулевые-значения-мьютексов-допустимы)
+ - [Копируйте срезы и карты на границах](#копируйте-срезы-и-карты-на-границах)
+ - [Используйте `defer` для очистки](#используйте-defer-для-очистки)
+ - [Размер канала — один или ноль](#размер-канала-один-или-ноль)
+ - [Начинайте перечисления с единицы](#начинайте-перечисления-с-единицы)
+ - [Используйте `"time"` для работы со
+ временем](#используйте-time-для-работы-со-временем)
+ - [Ошибки](#ошибки)
+ - [Типы ошибок](#типы-ошибок)
+ - [Обёртывание ошибок](#обёртывание-ошибок)
+ - [Именование ошибок](#именование-ошибок)
+ - [Обрабатывайте ошибки один раз](#обрабатывайте-ошибки-один-раз)
+ - [Обрабатывайте сбои утверждения типа](#обрабатывайте-сбои-утверждения-типа)
+ - [Не паникуйте](#не-паникуйте)
+ - [Используйте go.uber.org/atomic](#используйте-gouberorgatomic)
+ - [Избегайте изменяемых глобальных
+ переменных](#избегайте-изменяемых-глобальных-переменных)
+ - [Избегайте встраивания типов в публичные
+ структуры](#избегайте-встраивания-типов-в-публичные-структуры)
+ - [Избегайте использования встроенных
+ имён](#избегайте-использования-встроенных-имён)
+ - [Избегайте `init()`](#избегайте-init)
+ - [Завершение программы в main](#завершение-программы-в-main)
+ - [Завершайте программу один раз](#завершайте-программу-один-раз)
+ - [Используйте теги полей в структурах для
+ сериализации](#используйте-теги-полей-в-структурах-для-сериализации)
+ - [Не запускайте горутины по принципу «запустил и
+ забыл»](#не-запускайте-горутины-по-принципу-запустил-и-забыл)
+ - [Дожидайтесь завершения горутин](#дожидайтесь-завершения-горутин)
+ - [Не используйте горутины в `init()`](#не-используйте-горутины-в-init)
+- [Производительность](#производительность)
+ - [Предпочитайте strconv вместо fmt](#предпочитайте-strconv-вместо-fmt)
+ - [Избегайте повторного преобразования строк в
+ байты](#избегайте-повторного-преобразования-строк-в-байты)
+ - [Предпочитайте указание ёмкости
+ контейнеров](#предпочитайте-указание-ёмкости-контейнеров)
+- [Стиль](#стиль)
+ - [Избегайте слишком длинных строк](#избегайте-слишком-длинных-строк)
+ - [Будьте последовательны](#будьте-последовательны)
+ - [Группируйте схожие объявления](#группируйте-схожие-объявления)
+ - [Порядок групп импорта](#порядок-групп-импорта)
+ - [Имена пакетов](#имена-пакетов)
+ - [Имена функций](#имена-функций)
+ - [Псевдонимы импорта](#псевдонимы-импорта)
+ - [Группировка и порядок функций](#группировка-и-порядок-функций)
+ - [Уменьшайте вложенность](#уменьшайте-вложенность)
+ - [Избыточный Else](#избыточный-else)
+ - [Объявления переменных верхнего
+ уровня](#объявления-переменных-верхнего-уровня)
+ - [Префикс \_ для неэкспортируемых глобальных
+ переменных](#префикс-_-для-неэкспортируемых-глобальных-переменных)
+ - [Встраивание в структурах](#встраивание-в-структурах)
+ - [Объявление локальных переменных](#объявление-локальных-переменных)
+ - [nil — это валидный срез](#nil-это-валидный-срез)
+ - [Уменьшайте область видимости
+ переменных](#уменьшайте-область-видимости-переменных)
+ - [Избегайте «голых» параметров](#избегайте-голых-параметров)
+ - [Используйте сырые строковые литералы, чтобы избежать
+ экранирования](#используйте-сырые-строковые-литералы-чтобы-избежать-экранирования)
+ - [Инициализация структур](#инициализация-структур)
+ - [Используйте имена полей для инициализации
+ структур](#используйте-имена-полей-для-инициализации-структур)
+ - [Опускайте поля с нулевыми значениями в
+ структурах](#опускайте-поля-с-нулевыми-значениями-в-структурах)
+ - [Используйте `var` для структур с нулевыми
+ значениями](#используйте-var-для-структур-с-нулевыми-значениями)
+ - [Инициализация ссылок на структуры](#инициализация-ссылок-на-структуры)
+ - [Инициализация карт](#инициализация-карт)
+ - [Строки формата вне Printf](#строки-формата-вне-printf)
+ - [Именование функций в стиле Printf](#именование-функций-в-стиле-printf)
+- [Паттерны](#паттерны)
+ - [Табличные тесты](#табличные-тесты)
+ - [Функциональные опции](#функциональные-опции)
+- [Линтинг](#линтинг)
+
+## Введение
+
+Стили — это соглашения, которые регулируют наш код. Термин «стиль» немного
+неудачен, поскольку эти соглашения охватывают гораздо больше, чем просто
+форматирование исходных файлов — этим занимается `gofmt`.
+
+Цель этого руководства — управлять этой сложностью, подробно описывая, что
+следует и чего не следует делать при написании кода на Go в Uber. Эти правила
+существуют, чтобы кодовая база оставалась управляемой, позволяя при этом
+инженерам эффективно использовать возможности языка Go.
+
+Первоначально это руководство было создано [Прашантом
+Варанаси](https://github.com/prashantv) и [Саймоном
+Ньютоном](https://github.com/nomis52) как способ познакомить некоторых коллег с
+использованием Go. С годами оно было дополнено на основе отзывов других.
+
+В этом документе описаны идиоматические соглашения в коде на Go, которым мы
+следуем в Uber. Многие из них являются общими рекомендациями для Go, в то время
+как другие расширяют внешние ресурсы:
+
+1. [Effective Go](https://go.dev/doc/effective_go)
+2. [Go Common Mistakes](https://go.dev/wiki/CommonMistakes)
+3. [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments)
+
+Мы стремимся к тому, чтобы примеры кода были корректны для двух последних
+минорных версий [релизов Go](https://go.dev/doc/devel/release).
+
+Весь код должен быть свободен от ошибок при проверке с помощью `golint` и `go
+vet`. Мы рекомендуем настроить ваш редактор так, чтобы он:
+
+- Запускал `goimports` при сохранении
+- Запускал `golint` и `go vet` для проверки на ошибки
+
+Информацию о поддержке инструментов Go в редакторах можно найти здесь:
+https://go.dev/wiki/IDEsAndTextEditorPlugins
+
+## Рекомендации
+
+### Указатели на интерфейсы
+
+Почти никогда не нужен указатель на интерфейс. Следует передавать интерфейсы как
+значения — базовые данные при этом всё равно могут быть указателем.
+
+Интерфейс состоит из двух полей:
+
+1. Указатель на информацию, специфичную для типа. Можно думать об этом как о
+ «типе».
+2. Указатель на данные. Если хранимые данные являются указателем, они
+ сохраняются напрямую. Если хранимые данные являются значением, то сохраняется
+ указатель на это значение.
+
+Если нужно, чтобы методы интерфейса изменяли базовые данные, необходимо
+использовать указатель.
+
+### Проверка соответствия интерфейсу
+
+Проверяйте соответствие интерфейсу на этапе компиляции там, где это уместно. Это
+включает:
+
+- Экспортируемые типы, которые должны реализовывать определённые интерфейсы как
+ часть своего API-контракта
+- Экспортируемые или неэкспортируемые типы, которые являются частью коллекции
+ типов, реализующих один и тот же интерфейс
+- Другие случаи, когда нарушение интерфейса приведёт к поломке пользователей
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Handler struct {
+ // ...
+}
+
+
+
+func (h *Handler) ServeHTTP(
+ w http.ResponseWriter,
+ r *http.Request,
+) {
+ ...
+}
+```
+
+</td><td>
+
+```go
+type Handler struct {
+ // ...
+}
+
+var _ http.Handler = (*Handler)(nil)
+
+func (h *Handler) ServeHTTP(
+ w http.ResponseWriter,
+ r *http.Request,
+) {
+ // ...
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Выражение `var _ http.Handler = (*Handler)(nil)` не скомпилируется, если
+`*Handler` перестанет соответствовать интерфейсу `http.Handler`.
+
+Правая часть присваивания должна быть нулевым значением утверждаемого типа. Это
+`nil` для типов-указателей (например, `*Handler`), срезов и карт, и пустая
+структура для типов-структур.
+
+```go
+type LogHandler struct {
+ h http.Handler
+ log *zap.Logger
+}
+
+var _ http.Handler = LogHandler{}
+
+func (h LogHandler) ServeHTTP(
+ w http.ResponseWriter,
+ r *http.Request,
+) {
+ // ...
+}
+```
+
+### Получатели и интерфейсы
+
+Методы со значением-получателем могут вызываться как на указателях, так и на
+значениях. Методы с указателем-получателем могут вызываться только на указателях
+или [адресуемых значениях](https://go.dev/ref/spec#Method_values).
+
+Например,
+
+```go
+type S struct {
+ data string
+}
+
+func (s S) Read() string {
+ return s.data
+}
+
+func (s *S) Write(str string) {
+ s.data = str
+}
+
+// Мы не можем получить указатели на значения, хранящиеся в картах, потому что они не являются адресуемыми значениями.
+sVals := map[int]S{1: {"A"}}
+
+// Мы можем вызвать Read для значений, хранящихся в карте, потому что Read имеет получатель-значение, который не требует, чтобы значение было адресуемым.
+sVals[1].Read()
+
+// Мы не можем вызвать Write для значений, хранящихся в карте, потому что Write имеет получатель-указатель, и невозможно получить указатель на значение, хранящееся в карте.
+//
+// sVals[1].Write("test")
+
+sPtrs := map[int]*S{1: {"A"}}
+
+// Вы можете вызвать как Read, так и Write, если карта хранит указатели, потому что указатели по своей природе адресуемы.
+sPtrs[1].Read()
+sPtrs[1].Write("test")
+```
+
+Аналогично, интерфейс может быть удовлетворён указателем, даже если метод имеет
+получатель-значение.
+
+```go
+type F interface {
+ f()
+}
+
+type S1 struct{}
+
+func (s S1) f() {}
+
+type S2 struct{}
+
+func (s *S2) f() {}
+
+s1Val := S1{}
+s1Ptr := &S1{}
+s2Val := S2{}
+s2Ptr := &S2{}
+
+var i F
+i = s1Val
+i = s1Ptr
+i = s2Ptr
+
+// Следующее не скомпилируется, так как s2Val является значением, и для f нет получателя-значения.
+// i = s2Val
+```
+
+В Effective Go есть хорошее описание по теме [Указатели против
+значений](https://go.dev/doc/effective_go#pointers_vs_values).
+
+### Нулевые значения мьютексов допустимы
+
+Нулевое значение `sync.Mutex` и `sync.RWMutex` является допустимым, поэтому
+почти никогда не нужен указатель на мьютекс.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+mu := new(sync.Mutex)
+mu.Lock()
+```
+
+</td><td>
+
+```go
+var mu sync.Mutex
+mu.Lock()
+```
+
+</td></tr>
+</tbody></table>
+
+Если вы используете структуру по указателю, то мьютекс должен быть не
+указателем, а полем в ней. Не встраивайте мьютекс в структуру, даже если
+структура не экспортируется.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type SMap struct {
+ sync.Mutex
+
+ data map[string]string
+}
+
+func NewSMap() *SMap {
+ return &SMap{
+ data: make(map[string]string),
+ }
+}
+
+func (m *SMap) Get(k string) string {
+ m.Lock()
+ defer m.Unlock()
+
+ return m.data[k]
+}
+```
+
+</td><td>
+
+```go
+type SMap struct {
+ mu sync.Mutex
+
+ data map[string]string
+}
+
+func NewSMap() *SMap {
+ return &SMap{
+ data: make(map[string]string),
+ }
+}
+
+func (m *SMap) Get(k string) string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ return m.data[k]
+}
+```
+
+</td></tr>
+
+<tr><td>
+
+Поле `Mutex`, а также методы `Lock` и `Unlock` непреднамеренно становятся частью
+публичного API `SMap`.
+
+</td><td>
+
+Мьютекс и его методы являются деталями реализации `SMap`, скрытыми от его
+вызывающих сторон.
+
+</td></tr>
+</tbody></table>
+
+### Копируйте срезы и карты на границах
+
+Срезы и карты содержат указатели на базовые данные, поэтому будьте осторожны в
+ситуациях, когда их нужно скопировать.
+
+#### Получение срезов и карт
+
+Помните, что пользователи могут изменить карту или срез, которые вы получили в
+качестве аргумента, если сохраните ссылку на них.
+
+<table>
+<thead><tr><th>Плохо</th> <th>Хорошо</th></tr></thead>
+<tbody>
+<tr>
+<td>
+
+```go
+func (d *Driver) SetTrips(trips []Trip) {
+ d.trips = trips
+}
+
+trips := ...
+d1.SetTrips(trips)
+
+// Вы хотели изменить d1.trips?
+trips[0] = ...
+```
+
+</td>
+<td>
+
+```go
+func (d *Driver) SetTrips(trips []Trip) {
+ d.trips = make([]Trip, len(trips))
+ copy(d.trips, trips)
+}
+
+trips := ...
+d1.SetTrips(trips)
+
+// Теперь мы можем изменить trips[0], не затрагивая d1.trips.
+trips[0] = ...
+```
+
+</td>
+</tr>
+
+</tbody>
+</table>
+
+#### Возврат срезов и карт
+
+Аналогично, будьте осторожны с модификациями карт или срезов, раскрывающих
+внутреннее состояние.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Stats struct {
+ mu sync.Mutex
+ counters map[string]int
+}
+
+// Snapshot возвращает текущую статистику.
+func (s *Stats) Snapshot() map[string]int {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ return s.counters
+}
+
+// snapshot больше не защищён мьютексом, поэтому любой доступ к snapshot подвержен состоянию гонки.
+snapshot := stats.Snapshot()
+```
+
+</td><td>
+
+```go
+type Stats struct {
+ mu sync.Mutex
+ counters map[string]int
+}
+
+func (s *Stats) Snapshot() map[string]int {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ result := make(map[string]int, len(s.counters))
+ for k, v := range s.counters {
+ result[k] = v
+ }
+ return result
+}
+
+// Snapshot теперь является копией.
+snapshot := stats.Snapshot()
+```
+
+</td></tr>
+</tbody></table>
+
+### Используйте `defer` для очистки
+
+Используйте `defer` для очистки ресурсов, таких как файлы и блокировки.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+p.Lock()
+if p.count < 10 {
+ p.Unlock()
+ return p.count
+}
+
+p.count++
+newCount := p.count
+p.Unlock()
+
+return newCount
+
+// легко пропустить разблокировки из-за множественных возвратов
+```
+
+</td><td>
+
+```go
+p.Lock()
+defer p.Unlock()
+
+if p.count < 10 {
+ return p.count
+}
+
+p.count++
+return p.count
+
+// более читаемо
+```
+
+</td></tr>
+</tbody></table>
+
+`defer` имеет крайне малые накладные расходы, и его следует избегать только если
+вы можете доказать, что время выполнения вашей функции измеряется в
+наносекундах. Выигрыш в читаемости от использования `defer` стоит той мизерной
+стоимости, которую он вносит. Это особенно верно для больших методов, где
+присутствуют не только простые операции доступа к памяти, а другие вычисления
+более значимы, чем `defer`.
+
+### Размер канала — один или ноль
+
+Каналы обычно должны иметь размер один или быть небуферизированными. По
+умолчанию каналы небуферизированы и имеют размер ноль. Любой другой размер
+должен подвергаться тщательному анализу. Подумайте, как определяется размер, что
+предотвращает заполнение канала под нагрузкой и блокировку писателей, и что
+происходит, когда это случается.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// Должно хватить на всех!
+c := make(chan int, 64)
+```
+
+</td><td>
+
+```go
+// Размер один
+c := make(chan int, 1) // или
+// Небуферизированный канал, размер ноль
+c := make(chan int)
+```
+
+</td></tr>
+</tbody></table>
+
+### Начинайте перечисления с единицы
+
+Стандартный способ введения перечислений в Go — объявление пользовательского
+типа и группы `const` с `iota`. Поскольку переменные имеют значение по умолчанию
+0, обычно следует начинать перечисления с ненулевого значения.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Operation int
+
+const (
+ Add Operation = iota
+ Subtract
+ Multiply
+)
+
+// Add=0, Subtract=1, Multiply=2
+```
+
+</td><td>
+
+```go
+type Operation int
+
+const (
+ Add Operation = iota + 1
+ Subtract
+ Multiply
+)
+
+// Add=1, Subtract=2, Multiply=3
+```
+
+</td></tr>
+</tbody></table>
+
+Бывают случаи, когда использование нулевого значения имеет смысл, например,
+когда случай с нулевым значением является желаемым поведением по умолчанию.
+
+```go
+type LogOutput int
+
+const (
+ LogToStdout LogOutput = iota
+ LogToFile
+ LogToRemote
+)
+
+// LogToStdout=0, LogToFile=1, LogToRemote=2
+```
+
+<!-- TODO: раздел о String методах для перечислений -->
+
+### Используйте `"time"` для работы со временем
+
+Время — это сложно. Часто делаются неверные предположения о времени, включая
+следующие.
+
+1. В сутках 24 часа
+2. В часе 60 минут
+3. В неделе 7 дней
+4. В году 365 дней
+5. [И многое
+ другое](https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time)
+
+Например, _1_ означает, что добавление 24 часов к моменту времени не всегда даст
+новый календарный день.
+
+Поэтому всегда используйте пакет [`"time"`](https://pkg.go.dev/time) при работе
+со временем, так как он помогает безопаснее и точнее справляться с этими
+неверными предположениями.
+
+#### Используйте `time.Time` для моментов времени
+
+Используйте [`time.Time`](https://pkg.go.dev/time#Time) при работе с моментами
+времени и методы `time.Time` для сравнения, добавления или вычитания времени.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func isActive(now, start, stop int) bool {
+ return start <= now && now < stop
+}
+```
+
+</td><td>
+
+```go
+func isActive(now, start, stop time.Time) bool {
+ return (start.Before(now) || start.Equal(now)) && now.Before(stop)
+}
+```
+
+</td></tr>
+</tbody></table>
+
+#### Используйте `time.Duration` для промежутков времени
+
+Используйте [`time.Duration`](https://pkg.go.dev/time#Duration) при работе с
+промежутками времени.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func poll(delay int) {
+ for {
+ // ...
+ time.Sleep(time.Duration(delay) * time.Millisecond)
+ }
+}
+
+poll(10) // это секунды или миллисекунды?
+```
+
+</td><td>
+
+```go
+func poll(delay time.Duration) {
+ for {
+ // ...
+ time.Sleep(delay)
+ }
+}
+
+poll(10*time.Second)
+```
+
+</td></tr>
+</tbody></table>
+
+Возвращаясь к примеру добавления 24 часов к моменту времени, метод, который мы
+используем для добавления времени, зависит от намерения. Если мы хотим получить
+то же время суток, но на следующий календарный день, следует использовать
+[`Time.AddDate`](https://pkg.go.dev/time#Time.AddDate). Однако, если мы хотим
+момент времени, гарантированно наступающий через 24 часа после предыдущего,
+следует использовать [`Time.Add`](https://pkg.go.dev/time#Time.Add).
+
+```go
+newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */)
+maybeNewDay := t.Add(24 * time.Hour)
+```
+
+#### Используйте `time.Time` и `time.Duration` с внешними системами
+
+По возможности используйте `time.Duration` и `time.Time` при взаимодействии с
+внешними системами. Например:
+
+- Флаги командной строки: [`flag`](https://pkg.go.dev/flag) поддерживает
+ `time.Duration` через
+ [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration)
+- JSON: [`encoding/json`](https://pkg.go.dev/encoding/json) поддерживает
+ кодирование `time.Time` как строки [RFC
+ 3339](https://tools.ietf.org/html/rfc3339) через свой [`UnmarshalJSON`
+ метод](https://pkg.go.dev/time#Time.UnmarshalJSON)
+- SQL: [`database/sql`](https://pkg.go.dev/database/sql) поддерживает
+ преобразование столбцов `DATETIME` или `TIMESTAMP` в `time.Time` и обратно,
+ если базовый драйвер поддерживает это.
+- YAML: [`gopkg.in/yaml.v2`](https://pkg.go.dev/gopkg.in/yaml.v2) поддерживает
+ `time.Time` как строку [RFC 3339](https://tools.ietf.org/html/rfc3339) и
+ `time.Duration` через
+ [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration).
+
+Если невозможно использовать `time.Duration` в этих взаимодействиях, используйте
+`int` или `float64` и включайте единицу измерения в имя поля.
+
+Например, так как `encoding/json` не поддерживает `time.Duration`, единица
+измерения включается в имя поля.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// {"interval": 2}
+type Config struct {
+ Interval int `json:"interval"`
+}
+```
+
+</td><td>
+
+```go
+// {"intervalMillis": 2000}
+type Config struct {
+ IntervalMillis int `json:"intervalMillis"`
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Если невозможно использовать `time.Time` в этих взаимодействиях, если не
+согласована альтернатива, используйте `string` и форматируйте временные метки в
+соответствии с [RFC 3339](https://tools.ietf.org/html/rfc3339). Этот формат
+используется по умолчанию в
+[`Time.UnmarshalText`](https://pkg.go.dev/time#Time.UnmarshalText) и доступен
+для использования в `Time.Format` и `time.Parse` через
+[`time.RFC3339`](https://pkg.go.dev/time#RFC3339).
+
+Хотя на практике это обычно не проблема, имейте в виду, что пакет `"time"` не
+поддерживает разбор временных меток с високосными секундами
+([8728](https://github.com/golang/go/issues/8728)), а также не учитывает
+високосные секунды в вычислениях
+([15190](https://github.com/golang/go/issues/15190)). Если вы сравниваете два
+момента времени, разница не будет включать високосные секунды, которые могли
+произойти между этими моментами.
+
+### Ошибки
+
+#### Типы ошибок
+
+Есть несколько вариантов объявления ошибок. Рассмотрите следующее, прежде чем
+выбрать вариант, наиболее подходящий для вашего случая.
+
+- Нужно ли вызывающей стороне сопоставлять ошибку, чтобы обработать её? Если
+ да, мы должны поддерживать функции [`errors.Is`](https://pkg.go.dev/errors#Is)
+ или [`errors.As`](https://pkg.go.dev/errors#As) путём объявления переменной
+ ошибки верхнего уровня или пользовательского типа.
+- Сообщение об ошибке — статическая строка или динамическая строка, требующая
+ контекстной информации? Для первого случая можно использовать
+ [`errors.New`](https://pkg.go.dev/errors#New), но для второго необходимо
+ использовать [`fmt.Errorf`](https://pkg.go.dev/fmt#Errorf) или
+ пользовательский тип ошибки.
+- Мы распространяем новую ошибку, возвращённую нижележащей функцией? Если да,
+ см. [раздел об обёртывании ошибок](#обёртывание-ошибок).
+
+| Сопоставление ошибок? | Сообщение об ошибке | Рекомендация |
+| --------------------- | ------------------- | -------------------------------------------------------------------------- |
+| Нет | статическое | [`errors.New`](https://pkg.go.dev/errors#New) |
+| Нет | динамическое | [`fmt.Errorf`](https://pkg.go.dev/fmt#Errorf) |
+| Да | статическое | переменная верхнего уровня с [`errors.New`](https://pkg.go.dev/errors#New) |
+| Да | динамическое | пользовательский тип `error` |
+
+Например, используйте [`errors.New`](https://pkg.go.dev/errors#New) для ошибки
+со статической строкой. Экспортируйте эту ошибку как переменную, чтобы
+поддерживать её сопоставление с `errors.Is`, если вызывающей стороне нужно
+сопоставить и обработать эту ошибку.
+
+<table>
+<thead><tr><th>Без сопоставления ошибок</th><th>С сопоставлением ошибок</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// package foo
+
+func Open() error {
+ return errors.New("could not open")
+}
+
+// package bar
+
+if err := foo.Open(); err != nil {
+ // Не можем обработать ошибку.
+ panic("unknown error")
+}
+```
+
+</td><td>
+
+```go
+// package foo
+
+var ErrCouldNotOpen = errors.New("could not open")
+
+func Open() error {
+ return ErrCouldNotOpen
+}
+
+// package bar
+
+if err := foo.Open(); err != nil {
+ if errors.Is(err, foo.ErrCouldNotOpen) {
+ // обработать ошибку
+ } else {
+ panic("unknown error")
+ }
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Для ошибки с динамической строкой используйте
+[`fmt.Errorf`](https://pkg.go.dev/fmt#Errorf), если вызывающей стороне не нужно
+её сопоставлять, и пользовательский `error`, если нужно.
+
+<table>
+<thead><tr><th>Без сопоставления ошибок</th><th>С сопоставлением ошибок</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// package foo
+
+func Open(file string) error {
+ return fmt.Errorf("file %q not found", file)
+}
+
+// package bar
+
+if err := foo.Open("testfile.txt"); err != nil {
+ // Не можем обработать ошибку.
+ panic("unknown error")
+}
+```
+
+</td><td>
+
+```go
+// package foo
+
+type NotFoundError struct {
+ File string
+}
+
+func (e *NotFoundError) Error() string {
+ return fmt.Sprintf("file %q not found", e.File)
+}
+
+func Open(file string) error {
+ return &NotFoundError{File: file}
+}
+
+
+// package bar
+
+if err := foo.Open("testfile.txt"); err != nil {
+ var notFound *NotFoundError
+ if errors.As(err, &notFound) {
+ // обработать ошибку
+ } else {
+ panic("unknown error")
+ }
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Обратите внимание, что если вы экспортируете переменные или типы ошибок из
+пакета, они станут частью публичного API пакета.
+
+#### Обёртывание ошибок
+
+Есть три основных варианта распространения ошибок при неудачном вызове:
+
+- вернуть исходную ошибку как есть
+- добавить контекст с помощью `fmt.Errorf` и глагола `%w`
+- добавить контекст с помощью `fmt.Errorf` и глагола `%v`
+
+Возвращайте исходную ошибку как есть, если нечего добавить к контексту. Это
+сохраняет исходный тип и сообщение ошибки. Это хорошо подходит для случаев,
+когда базовое сообщение об ошибке содержит достаточно информации для
+отслеживания её происхождения.
+
+В противном случае добавляйте контекст к сообщению об ошибке, где это возможно,
+чтобы вместо расплывчатой ошибки вроде "connection refused" вы получали более
+полезные ошибки, такие как "call service foo: connection refused".
+
+Используйте `fmt.Errorf` для добавления контекста к вашим ошибкам, выбирая между
+глаголами `%w` или `%v` в зависимости от того, должна ли вызывающая сторона
+иметь возможность сопоставить и извлечь базовую причину.
+
+- Используйте `%w`, если вызывающая сторона должна иметь доступ к базовой
+ ошибке. Это хороший вариант по умолчанию для большинства обёрнутых ошибок, но
+ имейте в виду, что вызывающие стороны могут начать полагаться на это
+ поведение. Поэтому для случаев, когда обёрнутая ошибка является известной
+ `var` или типом, документируйте и тестируйте это как часть контракта вашей
+ функции.
+- Используйте `%v`, чтобы скрыть базовую ошибку. Вызывающие стороны не смогут её
+ сопоставить, но вы сможете переключиться на `%w` в будущем, если потребуется.
+
+При добавлении контекста к возвращаемым ошибкам сохраняйте контекст кратким,
+избегая фраз типа "failed to", которые констатируют очевидное и накапливаются по
+мере всплытия ошибки по стеку:
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+s, err := store.New()
+if err != nil {
+ return fmt.Errorf(
+ "failed to create new store: %w", err)
+}
+```
+
+</td><td>
+
+```go
+s, err := store.New()
+if err != nil {
+ return fmt.Errorf(
+ "new store: %w", err)
+}
+```
+
+</td></tr><tr><td>
+
+```plain
+failed to x: failed to y: failed to create new store: the error
+```
+
+</td><td>
+
+```plain
+x: y: new store: the error
+```
+
+</td></tr>
+</tbody></table>
+
+Однако, как только ошибка отправляется в другую систему, должно быть понятно,
+что сообщение является ошибкой (например, тег `err` или префикс "Failed" в
+логах).
+
+См. также [Don't just check errors, handle them
+gracefully](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully).
+
+#### Именование ошибок
+
+Для значений ошибок, хранящихся как глобальные переменные, используйте префикс
+`Err` или `err` в зависимости от того, экспортируются они или нет. Это указание
+заменяет [Префикс \_ для неэкспортируемых глобальных
+переменных](#префикс-_-для-неэкспортируемых-глобальных-переменных).
+
+```go
+var (
+ // Следующие две ошибки экспортируются, чтобы пользователи этого пакета могли сопоставлять их с errors.Is.
+
+ ErrBrokenLink = errors.New("link is broken")
+ ErrCouldNotOpen = errors.New("could not open")
+
+ // Эта ошибка не экспортируется, потому что мы не хотим делать её частью нашего публичного API. Мы всё ещё можем использовать её внутри пакета с errors.Is.
+
+ errNotFound = errors.New("not found")
+)
+```
+
+Для пользовательских типов ошибок используйте суффикс `Error` вместо этого.
+
+```go
+// Аналогично, эта ошибка экспортируется, чтобы пользователи этого пакета могли сопоставлять её с errors.As.
+
+type NotFoundError struct {
+ File string
+}
+func (e *NotFoundError) Error() string {
+ return fmt.Sprintf("file %q not found", e.File)
+}
+
+// А эта ошибка не экспортируется, потому что мы не хотим делать её частью публичного API. Мы всё ещё можем использовать её внутри пакета с errors.As.
+
+type resolveError struct {
+ Path string
+}
+
+func (e *resolveError) Error() string {
+ return fmt.Sprintf("resolve %q", e.Path)
+}
+```
+
+#### Обрабатывайте ошибки один раз
+
+Когда вызывающая сторона получает ошибку от вызываемой функции, она может
+обработать её различными способами в зависимости от того, что она знает об
+ошибке.
+
+К ним относятся, но не ограничиваются:
+
+- если контракт вызываемой функции определяет конкретные ошибки, сопоставить
+ ошибку с `errors.Is` или `errors.As` и обработать ветви по-разному
+- если ошибка является восстанавливаемой, залогировать ошибку и выполнить
+ graceful degradation
+- если ошибка представляет собой отказ в предметной области, вернуть чётко
+ определённую ошибку
+- вернуть ошибку, либо [обёрнутую](#обёртывание-ошибок), либо как есть
+
+Независимо от того, как вызывающая сторона обрабатывает ошибку, обычно она
+должна обрабатывать каждую ошибку только один раз. Вызывающая сторона не должна,
+например, логировать ошибку, а затем возвращать её, потому что _её_ вызывающие
+стороны тоже могут обработать ошибку.
+
+Например, рассмотрим следующие случаи:
+
+<table>
+<thead><tr><th>Описание</th><th>Код</th></tr></thead>
+<tbody>
+<tr><td>
+
+**Плохо**: Логировать ошибку и возвращать её
+
+Вызывающие стороны выше по стеку, вероятно, предпримут аналогичные действия с
+ошибкой. Это приведёт к большому количеству шума в логах приложения при малой
+пользе.
+
+</td><td>
+
+```go
+u, err := getUser(id)
+if err != nil {
+ // ПЛОХО: см. описание
+ log.Printf("Could not get user %q: %v", id, err)
+ return err
+}
+```
+
+</td></tr>
+<tr><td>
+
+**Хорошо**: Обернуть ошибку и вернуть её
+
+Вызывающие стороны выше по стеку обработают ошибку. Использование `%w`
+гарантирует, что они смогут сопоставить ошибку с `errors.Is` или `errors.As`,
+если это уместно.
+
+</td><td>
+
+```go
+u, err := getUser(id)
+if err != nil {
+ return fmt.Errorf("get user %q: %w", id, err)
+}
+```
+
+</td></tr>
+<tr><td>
+
+**Хорошо**: Логировать ошибку и выполнить graceful degradation
+
+Если операция не является строго необходимой, мы можем обеспечить
+деградировавший, но работающий опыт, восстановившись после ошибки.
+
+</td><td>
+
+```go
+if err := emitMetrics(); err != nil {
+ // Неудача при записи метрик не должна ломать приложение.
+ log.Printf("Could not emit metrics: %v", err)
+}
+
+```
+
+</td></tr>
+<tr><td>
+
+**Хорошо**: Сопоставить ошибку и выполнить graceful degradation
+
+Если вызываемая функция определяет конкретную ошибку в своём контракте и сбой
+является восстанавливаемым, сопоставьте этот случай ошибки и выполните graceful
+degradation. Для всех остальных случаев оберните ошибку и верните её.
+
+Вызывающие стороны выше по стеку обработают другие ошибки.
+
+</td><td>
+
+```go
+tz, err := getUserTimeZone(id)
+if err != nil {
+ if errors.Is(err, ErrUserNotFound) {
+ // Пользователь не существует. Используем UTC.
+ tz = time.UTC
+ } else {
+ return fmt.Errorf("get user %q: %w", id, err)
+ }
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Обрабатывайте сбои утверждения типа
+
+Форма утверждения типа с одним возвращаемым значением вызовет панику при
+неверном типе. Поэтому всегда используйте идиому "comma ok".
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+t := i.(string)
+```
+
+</td><td>
+
+```go
+t, ok := i.(string)
+if !ok {
+ // обработать ошибку корректно
+}
+```
+
+</td></tr>
+</tbody></table>
+
+<!-- TODO: Есть несколько ситуаций, где форма с одним присваиванием допустима. -->
+
+### Не паникуйте
+
+Код, работающий в production, должен избегать паник. Паники являются основной
+причиной [каскадных сбоев](https://en.wikipedia.org/wiki/Cascading_failure).
+Если возникает ошибка, функция должна вернуть ошибку и позволить вызывающей
+стороне решить, как её обработать.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func run(args []string) {
+ if len(args) == 0 {
+ panic("an argument is required")
+ }
+ // ...
+}
+
+func main() {
+ run(os.Args[1:])
+}
+```
+
+</td><td>
+
+```go
+func run(args []string) error {
+ if len(args) == 0 {
+ return errors.New("an argument is required")
+ }
+ // ...
+ return nil
+}
+
+func main() {
+ if err := run(os.Args[1:]); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Panic/recover — это не стратегия обработки ошибок. Программа должна паниковать
+только при возникновении чего-то невосстановимого, например, разыменовании nil.
+Исключением является инициализация программы: проблемы при запуске программы,
+которые должны её завершить, могут вызывать панику.
+
+```go
+var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))
+```
+
+Даже в тестах предпочитайте `t.Fatal` или `t.FailNow` вместо паник, чтобы
+гарантировать, что тест будет помечен как неудачный.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// func TestFoo(t *testing.T)
+
+f, err := os.CreateTemp("", "test")
+if err != nil {
+ panic("failed to set up test")
+}
+```
+
+</td><td>
+
+```go
+// func TestFoo(t *testing.T)
+
+f, err := os.CreateTemp("", "test")
+if err != nil {
+ t.Fatal("failed to set up test")
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Используйте go.uber.org/atomic
+
+Атомарные операции с пакетом [sync/atomic](https://pkg.go.dev/sync/atomic)
+работают с базовыми типами (`int32`, `int64` и т.д.), поэтому легко забыть
+использовать атомарную операцию для чтения или изменения переменных.
+
+[go.uber.org/atomic](https://pkg.go.dev/go.uber.org/atomic) добавляет
+безопасность типов к этим операциям, скрывая базовый тип. Кроме того, он
+включает удобный тип `atomic.Bool`.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type foo struct {
+ running int32 // atomic
+}
+
+func (f* foo) start() {
+ if atomic.SwapInt32(&f.running, 1) == 1 {
+ // уже работает…
+ return
+ }
+ // запустить Foo
+}
+
+func (f *foo) isRunning() bool {
+ return f.running == 1 // состояние гонки!
+}
+```
+
+</td><td>
+
+```go
+type foo struct {
+ running atomic.Bool
+}
+
+func (f *foo) start() {
+ if f.running.Swap(true) {
+ // уже работает…
+ return
+ }
+ // запустить Foo
+}
+
+func (f *foo) isRunning() bool {
+ return f.running.Load()
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Избегайте изменяемых глобальных переменных
+
+Избегайте изменения глобальных переменных, отдавая предпочтение внедрению
+зависимостей. Это относится как к указателям на функции, так и к другим видам
+значений.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// sign.go
+
+var _timeNow = time.Now
+
+func sign(msg string) string {
+ now := _timeNow()
+ return signWithTime(msg, now)
+}
+```
+
+</td><td>
+
+```go
+// sign.go
+
+type signer struct {
+ now func() time.Time
+}
+
+func newSigner() *signer {
+ return &signer{
+ now: time.Now,
+ }
+}
+
+func (s *signer) Sign(msg string) string {
+ now := s.now()
+ return signWithTime(msg, now)
+}
+```
+
+</td></tr>
+<tr><td>
+
+```go
+// sign_test.go
+
+func TestSign(t *testing.T) {
+ oldTimeNow := _timeNow
+ _timeNow = func() time.Time {
+ return someFixedTime
+ }
+ defer func() { _timeNow = oldTimeNow }()
+
+ assert.Equal(t, want, sign(give))
+}
+```
+
+</td><td>
+
+```go
+// sign_test.go
+
+func TestSigner(t *testing.T) {
+ s := newSigner()
+ s.now = func() time.Time {
+ return someFixedTime
+ }
+
+ assert.Equal(t, want, s.Sign(give))
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Избегайте встраивания типов в публичные структуры
+
+Такие встроенные типы раскрывают детали реализации, препятствуют эволюции типов
+и затрудняют чтение документации.
+
+Предположим, вы реализовали различные типы списков, используя общий
+`AbstractList`. Избегайте встраивания `AbstractList` в ваши конкретные
+реализации списков. Вместо этого напишите вручную только методы вашего
+конкретного списка, которые будут делегировать вызовы абстрактному списку.
+
+```go
+type AbstractList struct {}
+
+// Add добавляет сущность в список.
+func (l *AbstractList) Add(e Entity) {
+ // ...
+}
+
+// Remove удаляет сущность из списка.
+func (l *AbstractList) Remove(e Entity) {
+ // ...
+}
+```
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// ConcreteList — это список сущностей.
+type ConcreteList struct {
+ *AbstractList
+}
+```
+
+</td><td>
+
+```go
+// ConcreteList — это список сущностей.
+type ConcreteList struct {
+ list *AbstractList
+}
+
+// Add добавляет сущность в список.
+func (l *ConcreteList) Add(e Entity) {
+ l.list.Add(e)
+}
+
+// Remove удаляет сущность из списка.
+func (l *ConcreteList) Remove(e Entity) {
+ l.list.Remove(e)
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Go позволяет [встраивание типов](https://go.dev/doc/effective_go#embedding) как
+компромисс между наследованием и композицией. Внешний тип получает неявные копии
+методов встроенного типа. Эти методы по умолчанию делегируют вызовы тому же
+методу встроенного экземпляра.
+
+Структура также получает поле с тем же именем, что и тип. Таким образом, если
+встроенный тип является публичным, поле также является публичным. Для сохранения
+обратной совместимости каждая будущая версия внешнего типа должна сохранять
+встроенный тип.
+
+Встраивание типа редко необходимо. Это удобство, которое помогает избежать
+написания утомительных делегирующих методов.
+
+Даже встраивание совместимого интерфейса `AbstractList` вместо структуры дало бы
+разработчику больше гибкости для изменений в будущем, но всё равно раскрыло бы
+деталь, что конкретные списки используют абстрактную реализацию.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// AbstractList — это обобщённая реализация для различных видов списков сущностей.
+type AbstractList interface {
+ Add(Entity)
+ Remove(Entity)
+}
+
+// ConcreteList — это список сущностей.
+type ConcreteList struct {
+ AbstractList
+}
+```
+
+</td><td>
+
+```go
+// ConcreteList — это список сущностей.
+type ConcreteList struct {
+ list AbstractList
+}
+
+// Add добавляет сущность в список.
+func (l *ConcreteList) Add(e Entity) {
+ l.list.Add(e)
+}
+
+// Remove удаляет сущность из списка.
+func (l *ConcreteList) Remove(e Entity) {
+ l.list.Remove(e)
+}
+```
+
+</td></tr>
+</tbody></table>
+
+И в случае со встроенной структурой, и со встроенным интерфейсом встроенный тип
+накладывает ограничения на эволюцию типа.
+
+- Добавление методов во встроенный интерфейс — это breaking change.
+- Удаление методов из встроенной структуры — это breaking change.
+- Удаление встроенного типа — это breaking change.
+- Замена встроенного типа, даже на альтернативу, удовлетворяющую тому же
+ интерфейсу, — это breaking change.
+
+Хотя написание этих делегирующих методов утомительно, дополнительные усилия
+скрывают деталь реализации, оставляют больше возможностей для изменений, а также
+устраняют косвенность при обнаружении полного интерфейса `List` в документации.
+
+### Избегайте использования встроенных имён
+
+[Спецификация языка Go](https://go.dev/ref/spec) описывает несколько встроенных,
+[предобъявленных
+идентификаторов](https://go.dev/ref/spec#Predeclared_identifiers), которые не
+должны использоваться как имена в программах на Go.
+
+В зависимости от контекста повторное использование этих идентификаторов в
+качестве имён либо затеняет оригинал в текущей лексической области видимости (и
+любых вложенных областях), либо делает затронутый код запутанным. В лучшем
+случае компилятор пожалуется; в худшем — такой код может привести к скрытым,
+трудноуловимым ошибкам.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+var error string
+// `error` затеняет встроенный идентификатор
+
+// или
+
+func handleErrorMessage(error string) {
+ // `error` затеняет встроенный идентификатор
+}
+```
+
+</td><td>
+
+```go
+var errorMessage string
+// `error` ссылается на встроенный идентификатор
+
+// или
+
+func handleErrorMessage(msg string) {
+ // `error` ссылается на встроенный идентификатор
+}
+```
+
+</td></tr>
+<tr><td>
+
+```go
+type Foo struct {
+ // Хотя технически эти поля не создают затенение, поиск по строкам `error` или `string` теперь неоднозначен.
+ error error
+ string string
+}
+
+func (f Foo) Error() error {
+ // `error` и `f.error` визуально похожи
+ return f.error
+}
+
+func (f Foo) String() string {
+ // `string` и `f.string` визуально похожи
+ return f.string
+}
+```
+
+</td><td>
+
+```go
+type Foo struct {
+ // `error` и `string` теперь однозначны.
+ err error
+ str string
+}
+
+func (f Foo) Error() error {
+ return f.err
+}
+
+func (f Foo) String() string {
+ return f.str
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Обратите внимание, что компилятор не будет генерировать ошибки при использовании
+предобъявленных идентификаторов, но такие инструменты, как `go vet`, должны
+корректно указывать на эти и другие случаи затенения.
+
+### Избегайте `init()`
+
+Избегайте `init()` там, где это возможно. Когда `init()` неизбежен или
+желателен, код должен пытаться:
+
+1. Быть полностью детерминированным, независимо от среды программы или вызова.
+2. Избегать зависимости от порядка или побочных эффектов других функций
+ `init()`. Хотя порядок `init()` хорошо известен, код может меняться, и
+ поэтому зависимости между функциями `init()` могут сделать код хрупким и
+ подверженным ошибкам.
+3. Избегать доступа или манипуляции глобальным состоянием или состоянием
+ окружения, таким как информация о машине, переменные окружения, рабочая
+ директория, аргументы/вводы программы и т.д.
+4. Избегать ввода-вывода, включая файловую систему, сеть и системные вызовы.
+
+Код, который не может удовлетворить этим требованиям, вероятно, должен быть
+вспомогательной функцией, вызываемой как часть `main()` (или в другом месте
+жизненного цикла программы), или быть написан как часть самого `main()`. В
+частности, библиотеки, предназначенные для использования другими программами,
+должны особенно тщательно следить за полной детерминированностью и не выполнять
+"init magic".
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Foo struct {
+ // ...
+}
+
+var _defaultFoo Foo
+
+func init() {
+ _defaultFoo = Foo{
+ // ...
+ }
+}
+```
+
+</td><td>
+
+```go
+var _defaultFoo = Foo{
+ // ...
+}
+
+// или, лучше, для тестируемости:
+
+var _defaultFoo = defaultFoo()
+
+func defaultFoo() Foo {
+ return Foo{
+ // ...
+ }
+}
+```
+
+</td></tr>
+<tr><td>
+
+```go
+type Config struct {
+ // ...
+}
+
+var _config Config
+
+func init() {
+ // Плохо: зависит от текущей директории
+ cwd, _ := os.Getwd()
+
+ // Плохо: ввод-вывод
+ raw, _ := os.ReadFile(
+ path.Join(cwd, "config", "config.yaml"),
+ )
+
+ yaml.Unmarshal(raw, &_config)
+}
+```
+
+</td><td>
+
+```go
+type Config struct {
+ // ...
+}
+
+func loadConfig() Config {
+ cwd, err := os.Getwd()
+ // обработать err
+
+ raw, err := os.ReadFile(
+ path.Join(cwd, "config", "config.yaml"),
+ )
+ // обработать err
+
+ var config Config
+ yaml.Unmarshal(raw, &config)
+
+ return config
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Учитывая вышесказанное, некоторые ситуации, в которых `init()` может быть
+предпочтительнее или необходим, включают:
+
+- Сложные выражения, которые нельзя представить в виде одиночных присваиваний.
+- Подключаемые хуки, такие как диалекты `database/sql`, регистры типов
+ кодирования и т.д.
+- Оптимизации для [Google Cloud
+ Functions](https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations)
+ и другие формы детерминированного предварительного вычисления.
+
+### Завершение программы в main
+
+Программы на Go используют [`os.Exit`](https://pkg.go.dev/os#Exit) или
+[`log.Fatal*`](https://pkg.go.dev/log#Fatal) для немедленного завершения.
+(Паника — нехороший способ завершения программ, пожалуйста, [не
+паникуйте](#не-паникуйте).)
+
+Вызывайте `os.Exit` или `log.Fatal*` **только в `main()`**. Все остальные
+функции должны возвращать ошибки для сигнализации о сбое.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func main() {
+ body := readFile(path)
+ fmt.Println(body)
+}
+
+func readFile(path string) string {
+ f, err := os.Open(path)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ return string(b)
+}
+```
+
+</td><td>
+
+```go
+func main() {
+ body, err := readFile(path)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(body)
+}
+
+func readFile(path string) (string, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return "", err
+ }
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ return "", err
+ }
+
+ return string(b), nil
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Обоснование: Программы с несколькими функциями, которые завершают выполнение,
+создают несколько проблем:
+
+- Неочевидный поток управления: любая функция может завершить программу, поэтому
+ становится трудно рассуждать о потоке управления.
+- Сложность тестирования: функция, завершающая программу, также завершит тест,
+ который её вызывает. Это делает функцию трудной для тестирования и создаёт
+ риск пропуска других тестов, которые ещё не были запущены `go test`.
+- Пропущенная очистка: когда функция завершает программу, она пропускает вызовы
+ функций, поставленные в очередь с операторами `defer`. Это добавляет риск
+ пропуска важных задач очистки.
+
+#### Завершайте программу один раз
+
+По возможности старайтесь вызывать `os.Exit` или `log.Fatal` **не более одного
+раза** в вашем `main()`. Если есть несколько сценариев ошибок, которые
+останавливают выполнение программы, поместите эту логику в отдельную функцию и
+возвращайте из неё ошибки.
+
+Это приводит к сокращению функции `main()` и помещению всей ключевой
+бизнес-логики в отдельную, тестируемую функцию.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+package main
+
+func main() {
+ args := os.Args[1:]
+ if len(args) != 1 {
+ log.Fatal("missing file")
+ }
+ name := args[0]
+
+ f, err := os.Open(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ // Если мы вызовем log.Fatal после этой строки, f.Close не будет вызван.
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // ...
+}
+```
+
+</td><td>
+
+```go
+package main
+
+func main() {
+ if err := run(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func run() error {
+ args := os.Args[1:]
+ if len(args) != 1 {
+ return errors.New("missing file")
+ }
+ name := args[0]
+
+ f, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ b, err := io.ReadAll(f)
+ if err != nil {
+ return err
+ }
+
+ // ...
+}
+```
+
+</td></tr>
+</tbody></table>
+
+В примере выше используется `log.Fatal`, но рекомендация также применима к
+`os.Exit` или любому библиотечному коду, который вызывает `os.Exit`.
+
+```go
+func main() {
+ if err := run(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+```
+
+Вы можете изменить сигнатуру `run()`, чтобы она соответствовала вашим
+потребностям. Например, если ваша программа должна завершаться с определёнными
+кодами выхода при сбоях, `run()` может возвращать код выхода вместо ошибки. Это
+позволяет модульным тестам также напрямую проверять это поведение.
+
+```go
+func main() {
+ os.Exit(run(args))
+}
+
+func run() (exitCode int) {
+ // ...
+}
+```
+
+Более общо, обратите внимание, что функция `run()`, используемая в этих
+примерах, не является предписывающей. Существует гибкость в имени, сигнатуре и
+настройке функции `run()`. Среди прочего, вы можете:
+
+- принимать неразобранные аргументы командной строки (например,
+ `run(os.Args[1:])`)
+- разбирать аргументы командной строки в `main()` и передавать их в `run`
+- использовать пользовательский тип ошибки для передачи кода выхода обратно в
+ `main()`
+- помещать бизнес-логику на другой уровень абстракции, отличный от `package
+main`
+
+Эта рекомендация требует только, чтобы в вашем `main()` было единственное место,
+ответственное за фактическое завершение процесса.
+
+### Используйте теги полей в структурах для сериализации
+
+Любое поле структуры, которое сериализуется в JSON, YAML или другие форматы,
+поддерживающие именование полей на основе тегов, должно быть аннотировано
+соответствующим тегом.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Stock struct {
+ Price int
+ Name string
+}
+
+bytes, err := json.Marshal(Stock{
+ Price: 137,
+ Name: "UBER",
+})
+```
+
+</td><td>
+
+```go
+type Stock struct {
+ Price int `json:"price"`
+ Name string `json:"name"`
+ // Безопасно переименовать Name в Symbol.
+}
+
+bytes, err := json.Marshal(Stock{
+ Price: 137,
+ Name: "UBER",
+})
+```
+
+</td></tr>
+</tbody></table>
+
+Обоснование: Сериализованная форма структуры — это контракт между различными
+системами. Изменения в структуре сериализованной формы — включая имена полей —
+нарушают этот контракт. Указание имён полей внутри тегов делает контракт явным и
+защищает от случайного его нарушения при рефакторинге или переименовании полей.
+
+### Не запускайте горутины по принципу «запустил и забыл»
+
+Горутины легковесны, но не бесплатны: как минимум, они требуют памяти для своего
+стека и процессорного времени для планирования. Хотя эти затраты малы для
+типичного использования горутин, они могут вызвать значительные проблемы с
+производительностью, если горутины создаются в больших количествах без
+контролируемого времени жизни. Горутины с неуправляемым временем жизни также
+могут вызывать другие проблемы, например, мешать сборке мусора для
+неиспользуемых объектов и удерживать ресурсы, которые в противном случае больше
+не используются.
+
+Поэтому не допускайте утечек горутин в production коде. Используйте
+[go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak) для тестирования
+утечек горутин внутри пакетов, которые могут их создавать.
+
+В общем случае каждая горутина:
+
+- должна иметь предсказуемый момент, когда она перестанет выполняться; или
+- должен быть способ сигнализировать горутине, что ей следует остановиться
+
+В обоих случаях должен быть способ заблокировать выполнение и дождаться
+завершения горутины.
+
+Например:
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+go func() {
+ for {
+ flush()
+ time.Sleep(delay)
+ }
+}()
+```
+
+</td><td>
+
+```go
+var (
+ stop = make(chan struct{}) // сигнализирует горутине остановиться
+ done = make(chan struct{}) // сигнализирует нам, что горутина завершилась
+)
+go func() {
+ defer close(done)
+
+ ticker := time.NewTicker(delay)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ flush()
+ case <-stop:
+ return
+ }
+ }
+}()
+
+// В другом месте...
+close(stop) // сигнализировать горутине остановиться
+<-done // и ждать её завершения
+```
+
+</td></tr>
+<tr><td>
+
+Нет способа остановить эту горутину. Она будет работать, пока приложение не
+завершится.
+
+</td><td>
+
+Эту горутину можно остановить с помощью `close(stop)`, и мы можем дождаться её
+завершения с помощью `<-done`.
+
+</td></tr>
+</tbody></table>
+
+#### Дожидайтесь завершения горутин
+
+Для горутины, созданной системой, должен быть способ дождаться её завершения.
+Есть два популярных способа сделать это:
+
+- Используйте `sync.WaitGroup`, чтобы дождаться завершения нескольких горутин.
+ Делайте так, если нужно ждать несколько горутин.
+
+ ```go
+ var wg sync.WaitGroup
+ for i := 0; i < N; i++ {
+ wg.Go(...)
+ }
+
+ // Чтобы дождаться завершения всех:
+ wg.Wait()
+ ```
+
+- Добавьте ещё один `chan struct{}`, который горутина закроет по завершении.
+ Делайте так, если есть только одна горутина.
+
+ ```go
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ // ...
+ }()
+
+ // Чтобы дождаться завершения горутины:
+ <-done
+ ```
+
+#### Не используйте горутины в `init()`
+
+Функции `init()` не должны запускать горутины. См. также [Избегайте
+init()](#избегайте-init).
+
+Если пакету нужна фоновая горутина, он должен предоставлять объект,
+ответственный за управление временем жизни горутины. Этот объект должен
+предоставлять метод (`Close`, `Stop`, `Shutdown` и т.д.), который сигнализирует
+фоновой горутине об остановке и ждёт её завершения.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func init() {
+ go doWork()
+}
+
+func doWork() {
+ for {
+ // ...
+ }
+}
+```
+
+</td><td>
+
+```go
+type Worker struct{ /* ... */ }
+
+func NewWorker(...) *Worker {
+ w := &Worker{
+ stop: make(chan struct{}),
+ done: make(chan struct{}),
+ // ...
+ }
+ go w.doWork()
+}
+
+func (w *Worker) doWork() {
+ defer close(w.done)
+ for {
+ // ...
+ case <-w.stop:
+ return
+ }
+}
+
+// Shutdown говорит воркеру остановиться и ждёт, пока он завершится.
+func (w *Worker) Shutdown() {
+ close(w.stop)
+ <-w.done
+}
+```
+
+</td></tr>
+<tr><td>
+
+Создаёт фоновую горутину безусловно при экспорте этого пакета пользователем.
+Пользователь не имеет контроля над горутиной или способа её остановки.
+
+</td><td>
+
+Создаёт воркера только если пользователь его запрашивает. Предоставляет способ
+остановки воркера, чтобы пользователь мог освободить используемые им ресурсы.
+
+Обратите внимание, что следует использовать `WaitGroup`, если воркер управляет
+несколькими горутинами. См. [Дожидайтесь завершения
+горутин](#дожидайтесь-завершения-горутин).
+
+</td></tr>
+</tbody></table>
+
+## Производительность
+
+Рекомендации, специфичные для производительности, применяются только к «горячему
+пути» (hot path).
+
+### Предпочитайте strconv вместо fmt
+
+При преобразовании примитивов в строки и обратно `strconv` быстрее, чем `fmt`.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+for i := 0; i < b.N; i++ {
+ s := fmt.Sprint(rand.Int())
+}
+```
+
+</td><td>
+
+```go
+for i := 0; i < b.N; i++ {
+ s := strconv.Itoa(rand.Int())
+}
+```
+
+</td></tr>
+<tr><td>
+
+```plain
+BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
+```
+
+</td><td>
+
+```plain
+BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
+```
+
+</td></tr>
+</tbody></table>
+
+### Избегайте повторного преобразования строк в байты
+
+Не создавайте срезы байт из фиксированной строки повторно. Вместо этого
+выполните преобразование один раз и сохраните результат.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+for i := 0; i < b.N; i++ {
+ w.Write([]byte("Hello world"))
+}
+```
+
+</td><td>
+
+```go
+data := []byte("Hello world")
+for i := 0; i < b.N; i++ {
+ w.Write(data)
+}
+```
+
+</td></tr>
+<tr><td>
+
+```plain
+BenchmarkBad-4 50000000 22.2 ns/op
+```
+
+</td><td>
+
+```plain
+BenchmarkGood-4 500000000 3.25 ns/op
+```
+
+</td></tr>
+</tbody></table>
+
+### Предпочитайте указание ёмкости контейнеров
+
+По возможности указывайте ёмкость контейнеров, чтобы выделить память для
+контейнера заранее. Это минимизирует последующие выделения памяти (из-за
+копирования и изменения размера контейнера) при добавлении элементов.
+
+#### Указание подсказки ёмкости для карт
+
+По возможности предоставляйте подсказку ёмкости при инициализации карт с помощью
+`make()`.
+
+```go
+make(map[T1]T2, hint)
+```
+
+Предоставление подсказки ёмкости для `make()` пытается правильно определить
+размер карты при инициализации, что уменьшает необходимость её роста и выделений
+памяти при добавлении элементов.
+
+Обратите внимание, что в отличие от срезов, подсказки ёмкости для карт не
+гарантируют полного, упреждающего выделения, а используются для приблизительного
+определения количества необходимых корзин хэш-карты. Следовательно, выделения
+памяти всё ещё могут происходить при добавлении элементов в карту, даже до
+указанной ёмкости.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+m := make(map[string]os.FileInfo)
+
+files, _ := os.ReadDir("./files")
+for _, f := range files {
+ m[f.Name()] = f
+}
+```
+
+</td><td>
+
+```go
+
+files, _ := os.ReadDir("./files")
+
+m := make(map[string]os.DirEntry, len(files))
+for _, f := range files {
+ m[f.Name()] = f
+}
+```
+
+</td></tr>
+<tr><td>
+
+`m` создаётся без подсказки размера; при присваивании может быть больше
+выделений памяти.
+
+</td><td>
+
+`m` создаётся с подсказкой размера; при присваивании может быть меньше выделений
+памяти.
+
+</td></tr>
+</tbody></table>
+
+#### Указание ёмкости срезов
+
+По возможности предоставляйте подсказку ёмкости при инициализации срезов с
+помощью `make()`, особенно при использовании `append`.
+
+```go
+make([]T, length, capacity)
+```
+
+В отличие от карт, ёмкость среза — это не подсказка: компилятор выделит
+достаточно памяти для ёмкости среза, предоставленной в `make()`, что означает,
+что последующие операции `append()` не будут приводить к выделениям памяти (пока
+длина среза не совпадёт с ёмкостью, после чего любые добавления потребуют
+изменения размера для хранения дополнительных элементов).
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+for n := 0; n < b.N; n++ {
+ data := make([]int, 0)
+ for k := 0; k < size; k++{
+ data = append(data, k)
+ }
+}
+```
+
+</td><td>
+
+```go
+for n := 0; n < b.N; n++ {
+ data := make([]int, 0, size)
+ for k := 0; k < size; k++{
+ data = append(data, k)
+ }
+}
+```
+
+</td></tr>
+<tr><td>
+
+```plain
+BenchmarkBad-4 100000000 2.48s
+```
+
+</td><td>
+
+```plain
+BenchmarkGood-4 100000000 0.21s
+```
+
+</td></tr>
+</tbody></table>
+
+## Стиль
+
+### Избегайте слишком длинных строк
+
+Избегайте строк кода, которые заставляют читателей прокручивать по горизонтали
+или слишком сильно поворачивать голову.
+
+Мы рекомендуем мягкое ограничение длины строки в **99 символов**. Авторы должны
+стараться переносить строки до достижения этого предела, но это не строгое
+ограничение. Коду разрешено превышать этот лимит.
+
+### Будьте последовательны
+
+Некоторые рекомендации, изложенные в этом документе, можно оценить объективно;
+другие ситуативны, контекстны или субъективны.
+
+Прежде всего, **будьте последовательны**.
+
+Последовательный код легче поддерживать, легче осмыслить, требует меньше
+когнитивных усилий и легче переносить или обновлять по мере появления новых
+соглашений или исправления классов ошибок.
+
+И наоборот, наличие множества различных или конфликтующих стилей в одной кодовой
+базе создаёт накладные расходы на поддержку, неопределённость и когнитивный
+диссонанс, что напрямую может способствовать снижению скорости разработки,
+болезненным код-ревью и ошибкам.
+
+При применении этих рекомендаций к кодовой базе рекомендуется вносить изменения
+на уровне пакета (или выше): применение на уровне подпакета нарушает указанную
+выше проблему, внося несколько стилей в один код.
+
+### Группируйте схожие объявления
+
+Go поддерживает группировку схожих объявлений.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+import "a"
+import "b"
+```
+
+</td><td>
+
+```go
+import (
+ "a"
+ "b"
+)
+```
+
+</td></tr>
+</tbody></table>
+
+Это также относится к константам, переменным и объявлениям типов.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+
+const a = 1
+const b = 2
+
+
+
+var a = 1
+var b = 2
+
+
+
+type Area float64
+type Volume float64
+```
+
+</td><td>
+
+```go
+const (
+ a = 1
+ b = 2
+)
+
+var (
+ a = 1
+ b = 2
+)
+
+type (
+ Area float64
+ Volume float64
+)
+```
+
+</td></tr>
+</tbody></table>
+
+Группируйте только связанные объявления. Не группируйте несвязанные объявления.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Operation int
+
+const (
+ Add Operation = iota + 1
+ Subtract
+ Multiply
+ EnvVar = "MY_ENV"
+)
+```
+
+</td><td>
+
+```go
+type Operation int
+
+const (
+ Add Operation = iota + 1
+ Subtract
+ Multiply
+)
+
+const EnvVar = "MY_ENV"
+```
+
+</td></tr>
+</tbody></table>
+
+Группы не ограничены в том, где могут использоваться. Например, их можно
+использовать внутри функций.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func f() string {
+ red := color.New(0xff0000)
+ green := color.New(0x00ff00)
+ blue := color.New(0x0000ff)
+
+ // ...
+}
+```
+
+</td><td>
+
+```go
+func f() string {
+ var (
+ red = color.New(0xff0000)
+ green = color.New(0x00ff00)
+ blue = color.New(0x0000ff)
+ )
+
+ // ...
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Исключение: Объявления переменных, особенно внутри функций, должны
+группироваться вместе, если они объявлены рядом с другими переменными. Делайте
+так для переменных, объявленных вместе, даже если они не связаны.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func (c *client) request() {
+ caller := c.name
+ format := "json"
+ timeout := 5*time.Second
+ var err error
+
+ // ...
+}
+```
+
+</td><td>
+
+```go
+func (c *client) request() {
+ var (
+ caller = c.name
+ format = "json"
+ timeout = 5*time.Second
+ err error
+ )
+
+ // ...
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Порядок групп импорта
+
+Должно быть две группы импорта:
+
+- Стандартная библиотека
+- Все остальные
+
+Это группировка, применяемая по умолчанию в `goimports`.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+import (
+ "fmt"
+ "os"
+ "go.uber.org/atomic"
+ "golang.org/x/sync/errgroup"
+)
+```
+
+</td><td>
+
+```go
+import (
+ "fmt"
+ "os"
+
+ "go.uber.org/atomic"
+ "golang.org/x/sync/errgroup"
+)
+```
+
+</td></tr>
+</tbody></table>
+
+### Имена пакетов
+
+При именовании пакетов выбирайте имя, которое:
+
+- Состоит только из строчных букв. Без заглавных букв и подчёркиваний.
+- Не требует переименования с использованием именованных импортов в большинстве
+ мест вызова.
+- Короткое и ёмкое. Помните, что имя полностью указывается в каждом месте
+ вызова.
+- Не во множественном числе. Например, `net/url`, а не `net/urls`.
+- Не "common", "util", "shared" или "lib". Это плохие, неинформативные имена.
+
+См. также [Package Names](https://go.dev/blog/package-names) и [Style guideline
+for Go packages](https://rakyll.org/style-packages/).
+
+### Имена функций
+
+Мы следуем соглашению сообщества Go об использовании [MixedCaps для имён
+функций](https://go.dev/doc/effective_go#mixed-caps). Исключение делается для
+тестовых функций, которые могут содержать подчёркивания для группировки
+связанных тестовых случаев, например, `TestMyFunction_WhatIsBeingTested`.
+
+### Псевдонимы импорта
+
+Псевдонимы импорта должны использоваться, если имя пакета не совпадает с
+последним элементом пути импорта.
+
+```go
+import (
+ "net/http"
+
+ client "example.com/client-go"
+ trace "example.com/trace/v2"
+)
+```
+
+Во всех остальных сценариях псевдонимы импорта следует избегать, если нет
+прямого конфликта между импортами.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+import (
+ "fmt"
+ "os"
+ runtimetrace "runtime/trace"
+
+ nettrace "golang.net/x/trace"
+)
+```
+
+</td><td>
+
+```go
+import (
+ "fmt"
+ "os"
+ "runtime/trace"
+
+ nettrace "golang.net/x/trace"
+)
+```
+
+</td></tr>
+</tbody></table>
+
+### Группировка и порядок функций
+
+- Функции должны быть отсортированы в приблизительном порядке вызовов.
+- Функции в файле должны быть сгруппированы по получателю.
+
+Следовательно, экспортируемые функции должны появляться первыми в файле после
+определений `struct`, `const`, `var`.
+
+`newXYZ()`/`NewXYZ()` может появиться после определения типа, но до остальных
+методов получателя.
+
+Поскольку функции группируются по получателю, простые вспомогательные функции
+должны появляться ближе к концу файла.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func (s *something) Cost() {
+ return calcCost(s.weights)
+}
+
+type something struct{ ... }
+
+func calcCost(n []int) int {...}
+
+func (s *something) Stop() {...}
+
+func newSomething() *something {
+ return &something{}
+}
+```
+
+</td><td>
+
+```go
+type something struct{ ... }
+
+func newSomething() *something {
+ return &something{}
+}
+
+func (s *something) Cost() {
+ return calcCost(s.weights)
+}
+
+func (s *something) Stop() {...}
+
+func calcCost(n []int) int {...}
+```
+
+</td></tr>
+</tbody></table>
+
+### Уменьшайте вложенность
+
+Код должен уменьшать вложенность, где это возможно, обрабатывая случаи
+ошибок/особые условия первыми и возвращаясь рано или продолжая цикл. Уменьшайте
+количество кода, вложенного на несколько уровней.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+for _, v := range data {
+ if v.F1 == 1 {
+ v = process(v)
+ if err := v.Call(); err == nil {
+ v.Send()
+ } else {
+ return err
+ }
+ } else {
+ log.Printf("Invalid v: %v", v)
+ }
+}
+```
+
+</td><td>
+
+```go
+for _, v := range data {
+ if v.F1 != 1 {
+ log.Printf("Invalid v: %v", v)
+ continue
+ }
+
+ v = process(v)
+ if err := v.Call(); err != nil {
+ return err
+ }
+ v.Send()
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Избыточный Else
+
+Если переменная устанавливается в обеих ветках if, это можно заменить одним if.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+var a int
+if b {
+ a = 100
+} else {
+ a = 10
+}
+```
+
+</td><td>
+
+```go
+a := 10
+if b {
+ a = 100
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Объявления переменных верхнего уровня
+
+На верхнем уровне используйте стандартное ключевое слово `var`. Не указывайте
+тип, если он не отличается от типа выражения.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+var _s string = F()
+
+func F() string { return "A" }
+```
+
+</td><td>
+
+```go
+var _s = F()
+// Поскольку F уже указывает, что возвращает строку, нам не нужно снова указывать тип.
+
+func F() string { return "A" }
+```
+
+</td></tr>
+</tbody></table>
+
+Указывайте тип, если тип выражения не совпадает точно с желаемым типом.
+
+```go
+type myError struct{}
+
+func (myError) Error() string { return "error" }
+
+func F() myError { return myError{} }
+
+var _e error = F()
+// F возвращает объект типа myError, но мы хотим error.
+```
+
+### Префикс \_ для неэкспортируемых глобальных переменных
+
+Добавляйте префикс `_` к неэкспортируемым переменным и константам верхнего
+уровня, чтобы было ясно, что они являются глобальными символами, когда они
+используются.
+
+Обоснование: Переменные и константы верхнего уровня имеют область видимости
+пакета. Использование общего имени делает лёгким случайное использование
+неправильного значения в другом файле.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// foo.go
+
+const (
+ defaultPort = 8080
+ defaultUser = "user"
+)
+
+// bar.go
+
+func Bar() {
+ defaultPort := 9090
+ ...
+ fmt.Println("Default port", defaultPort)
+
+ // Мы не увидим ошибку компиляции, если первая строка Bar() будет удалена.
+}
+```
+
+</td><td>
+
+```go
+// foo.go
+
+const (
+ _defaultPort = 8080
+ _defaultUser = "user"
+)
+```
+
+</td></tr>
+</tbody></table>
+
+**Исключение**: Неэкспортируемые значения ошибок могут использовать префикс
+`err` без подчёркивания. См. [Именование ошибок](#именование-ошибок).
+
+### Встраивание в структурах
+
+Встроенные типы должны находиться в начале списка полей структуры, и должна быть
+пустая строка, отделяющая встроенные поля от обычных полей.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type Client struct {
+ version int
+ http.Client
+}
+```
+
+</td><td>
+
+```go
+type Client struct {
+ http.Client
+
+ version int
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Встраивание должно обеспечивать ощутимую пользу, например, добавлять или
+расширять функциональность семантически-уместным способом. Оно должно делать это
+без каких-либо негативных последствий для пользователя (см. также: [Избегайте
+встраивания типов в публичные
+структуры](#избегайте-встраивания-типов-в-публичные-структуры)).
+
+Исключение: Мьютексы не должны встраиваться, даже в неэкспортируемые типы. См.
+также: [Нулевые значения мьютексов
+допустимы](#нулевые-значения-мьютексов-допустимы).
+
+Встраивание **НЕ должно**:
+
+- Быть чисто косметическим или ориентированным на удобство.
+- Усложнять создание или использование внешних типов.
+- Влиять на нулевые значения внешних типов. Если внешний тип имеет полезное
+ нулевое значение, он должен сохранять его после встраивания внутреннего типа.
+- Раскрывать несвязанные функции или поля внешнего типа как побочный эффект
+ встраивания внутреннего типа.
+- Раскрывать неэкспортируемые типы.
+- Влиять на семантику копирования внешних типов.
+- Менять API или семантику типов внешних типов.
+- Встраивать неканоническую форму внутреннего типа.
+- Раскрывать детали реализации внешнего типа.
+- Позволять пользователям наблюдать или контролировать внутренности типа.
+- Менять общее поведение внутренних функций через обёртывание таким образом,
+ который может удивить пользователей.
+
+Проще говоря, встраивайте осознанно и преднамеренно. Хороший тест: "все ли эти
+экспортируемые внутренние методы/поля были бы добавлены напрямую к внешнему
+типу"; если ответ "некоторые" или "нет", не встраивайте внутренний тип —
+используйте поле.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+type A struct {
+ // Плохо: A.Lock() и A.Unlock() теперь доступны, не предоставляют функциональной пользы и позволяют пользователям контролировать детали внутренностей A.
+ sync.Mutex
+}
+```
+
+</td><td>
+
+```go
+type countingWriteCloser struct {
+ // Хорошо: Write() предоставлен на этом внешнем уровне для конкретной цели и делегирует работу Write() внутреннего типа.
+ io.WriteCloser
+
+ count int
+}
+
+func (w *countingWriteCloser) Write(bs []byte) (int, error) {
+ w.count += len(bs)
+ return w.WriteCloser.Write(bs)
+}
+```
+
+</td></tr>
+<tr><td>
+
+```go
+type Book struct {
+ // Плохо: указатель меняет полезность нулевого значения
+ io.ReadWriter
+
+ // другие поля
+}
+
+// позже
+var b Book
+b.Read(...) // panic: nil pointer
+b.String() // panic: nil pointer
+b.Write(...) // panic: nil pointer
+```
+
+</td><td>
+
+```go
+type Book struct {
+ // Хорошо: имеет полезное нулевое значение
+ bytes.Buffer
+
+ // другие поля
+}
+
+// позже
+
+var b Book
+b.Read(...) // ok
+b.String() // ok
+b.Write(...) // ok
+```
+
+</td></tr>
+<tr><td>
+
+```go
+type Client struct {
+ sync.Mutex
+ sync.WaitGroup
+ bytes.Buffer
+ url.URL
+}
+```
+
+</td><td>
+
+```go
+type Client struct {
+ mtx sync.Mutex
+ wg sync.WaitGroup
+ buf bytes.Buffer
+ url url.URL
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Объявление локальных переменных
+
+Короткое объявление переменных (`:=`) должно использоваться, если переменная
+явно устанавливается в некоторое значение.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+var s = "foo"
+```
+
+</td><td>
+
+```go
+s := "foo"
+```
+
+</td></tr>
+</tbody></table>
+
+Однако бывают случаи, когда значение по умолчанию понятнее при использовании
+ключевого слова `var`. [Объявление пустых
+срезов](https://go.dev/wiki/CodeReviewComments#declaring-empty-slices),
+например.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func f(list []int) {
+ filtered := []int{}
+ for _, v := range list {
+ if v > 10 {
+ filtered = append(filtered, v)
+ }
+ }
+}
+```
+
+</td><td>
+
+```go
+func f(list []int) {
+ var filtered []int
+ for _, v := range list {
+ if v > 10 {
+ filtered = append(filtered, v)
+ }
+ }
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### nil — это валидный срез
+
+`nil` — это валидный срез длины 0. Это означает, что:
+
+- Не следует явно возвращать срез длины ноль. Возвращайте `nil` вместо этого.
+
+ <table>
+ <thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+ <tbody>
+ <tr><td>
+
+ ```go
+ if x == "" {
+ return []int{}
+ }
+ ```
+
+ </td><td>
+
+ ```go
+ if x == "" {
+ return nil
+ }
+ ```
+
+ </td></tr>
+ </tbody></table>
+
+- Чтобы проверить, пуст ли срез, всегда используйте `len(s) == 0`. Не проверяйте
+ на `nil`.
+
+ <table>
+ <thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+ <tbody>
+ <tr><td>
+
+ ```go
+ func isEmpty(s []string) bool {
+ return s == nil
+ }
+ ```
+
+ </td><td>
+
+ ```go
+ func isEmpty(s []string) bool {
+ return len(s) == 0
+ }
+ ```
+
+ </td></tr>
+ </tbody></table>
+
+- Нулевое значение (срез, объявленный с `var`) можно использовать сразу без
+`make()`.
+
+ <table>
+ <thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+ <tbody>
+ <tr><td>
+
+```go
+nums := []int{}
+// или, nums := make([]int)
+
+if add1 {
+ nums = append(nums, 1)
+}
+
+if add2 {
+ nums = append(nums, 2)
+}
+```
+
+</td><td>
+
+```go
+var nums []int
+
+if add1 {
+ nums = append(nums, 1)
+}
+
+if add2 {
+ nums = append(nums, 2)
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Помните, что хотя nil-срез является валидным срезом, он не эквивалентен
+выделенному срезу длины 0 — один является nil, а другой нет — и они могут
+обрабатываться по-разному в разных ситуациях (например, при сериализации).
+
+### Уменьшайте область видимости переменных
+
+По возможности уменьшайте область видимости переменных и констант. Не уменьшайте
+область видимости, если это противоречит [Уменьшению
+вложенности](#уменьшайте-вложенность).
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+err := os.WriteFile(name, data, 0644)
+if err != nil {
+ return err
+}
+```
+
+</td><td>
+
+```go
+if err := os.WriteFile(name, data, 0644); err != nil {
+ return err
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Если вам нужен результат вызова функции вне if, то не следует пытаться уменьшить
+область видимости.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+if data, err := os.ReadFile(name); err == nil {
+ err = cfg.Decode(data)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(cfg)
+ return nil
+} else {
+ return err
+}
+```
+
+</td><td>
+
+```go
+data, err := os.ReadFile(name)
+if err != nil {
+ return err
+}
+
+if err := cfg.Decode(data); err != nil {
+ return err
+}
+
+fmt.Println(cfg)
+return nil
+```
+
+</td></tr>
+</tbody></table>
+
+Константам не нужно быть глобальными, если они не используются в нескольких
+функциях или файлах или являются частью внешнего контракта пакета.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+const (
+ _defaultPort = 8080
+ _defaultUser = "user"
+)
+
+func Bar() {
+ fmt.Println("Default port", _defaultPort)
+}
+```
+
+</td><td>
+
+```go
+func Bar() {
+ const (
+ defaultPort = 8080
+ defaultUser = "user"
+ )
+ fmt.Println("Default port", defaultPort)
+}
+```
+
+</td></tr>
+</tbody></table>
+
+### Избегайте «голых» параметров
+
+«Голые» параметры в вызовах функций могут ухудшать читаемость. Добавляйте
+комментарии в стиле C (`/* ... */`) для имён параметров, когда их значение
+неочевидно.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// func printInfo(name string, isLocal, done bool)
+
+printInfo("foo", true, true)
+```
+
+</td><td>
+
+```go
+// func printInfo(name string, isLocal, done bool)
+
+printInfo("foo", true /* isLocal */, true /* done */)
+```
+
+</td></tr>
+</tbody></table>
+
+Ещё лучше заменить «голые» типы `bool` пользовательскими типами для более
+читаемого и типобезопасного кода. Это позволяет в будущем иметь более двух
+состояний (true/false) для этого параметра.
+
+```go
+type Region int
+
+const (
+ UnknownRegion Region = iota
+ Local
+)
+
+type Status int
+
+const (
+ StatusReady Status = iota + 1
+ StatusDone
+ // Возможно, в будущем у нас будет StatusInProgress.
+)
+
+func printInfo(name string, region Region, status Status)
+```
+
+### Используйте сырые строковые литералы, чтобы избежать экранирования
+
+Go поддерживает [сырые строковые
+литералы](https://go.dev/ref/spec#raw_string_lit), которые могут занимать
+несколько строк и включать кавычки. Используйте их, чтобы избежать ручного
+экранирования строк, которое гораздо труднее читать.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+wantError := "unknown name:\"test\""
+```
+
+</td><td>
+
+```go
+wantError := `unknown error:"test"`
+```
+
+</td></tr>
+</tbody></table>
+
+### Инициализация структур
+
+#### Используйте имена полей для инициализации структур
+
+Вы почти всегда должны указывать имена полей при инициализации структур. Теперь
+это обеспечивается [`go vet`](https://pkg.go.dev/cmd/vet).
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+k := User{"John", "Doe", true}
+```
+
+</td><td>
+
+```go
+k := User{
+ FirstName: "John",
+ LastName: "Doe",
+ Admin: true,
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Исключение: Имена полей _могут_ быть опущены в таблицах тестов, когда полей 3
+или меньше.
+
+```go
+tests := []struct{
+ op Operation
+ want string
+}{
+ {Add, "add"},
+ {Subtract, "subtract"},
+}
+```
+
+#### Опускайте поля с нулевыми значениями в структурах
+
+При инициализации структур с именами полей опускайте поля, имеющие нулевые
+значения, если они не предоставляют значимый контекст. В противном случае
+позвольте Go автоматически установить их в нулевые значения.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+user := User{
+ FirstName: "John",
+ LastName: "Doe",
+ MiddleName: "",
+ Admin: false,
+}
+```
+
+</td><td>
+
+```go
+user := User{
+ FirstName: "John",
+ LastName: "Doe",
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Это помогает уменьшить шум для читателей, опуская значения, которые являются
+стандартными в данном контексте. Указываются только значимые значения.
+
+Включайте нулевые значения, когда имена полей предоставляют значимый контекст.
+Например, тестовые случаи в [Табличных тестах](#табличные-тесты) могут выиграть
+от указания имён полей, даже когда они имеют нулевые значения.
+
+```go
+tests := []struct{
+ give string
+ want int
+}{
+ {give: "0", want: 0},
+ // ...
+}
+```
+
+#### Используйте `var` для структур с нулевыми значениями
+
+Когда все поля структуры опущены в объявлении, используйте форму `var` для
+объявления структуры.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+user := User{}
+```
+
+</td><td>
+
+```go
+var user User
+```
+
+</td></tr>
+</tbody></table>
+
+Это отличает структуры с нулевыми значениями от тех, у которых есть ненулевые
+поля, аналогично различию, создаваемому для [инициализации
+карт](#инициализация-карт), и соответствует тому, как мы предпочитаем [объявлять
+пустые срезы](https://go.dev/wiki/CodeReviewComments#declaring-empty-slices).
+
+#### Инициализация ссылок на структуры
+
+Используйте `&T{}` вместо `new(T)` при инициализации ссылок на структуры, чтобы
+это было согласовано с инициализацией структур.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+sval := T{Name: "foo"}
+
+// несогласованно
+sptr := new(T)
+sptr.Name = "bar"
+```
+
+</td><td>
+
+```go
+sval := T{Name: "foo"}
+
+sptr := &T{Name: "bar"}
+```
+
+</td></tr>
+</tbody></table>
+
+### Инициализация карт
+
+Предпочитайте `make(..)` для пустых карт и карт, заполняемых программно. Это
+делает инициализацию карт визуально отличной от объявления и позволяет легко
+добавить подсказку размера позже, если она доступна.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+var (
+ // m1 безопасна для чтения и записи;
+ // m2 вызовет панику при записи.
+ m1 = map[T1]T2{}
+ m2 map[T1]T2
+)
+```
+
+</td><td>
+
+```go
+var (
+ // m1 безопасна для чтения и записи;
+ // m2 вызовет панику при записи.
+ m1 = make(map[T1]T2)
+ m2 map[T1]T2
+)
+```
+
+</td></tr>
+<tr><td>
+
+Объявление и инициализация визуально похожи.
+
+</td><td>
+
+Объявление и инициализация визуально различны.
+
+</td></tr>
+</tbody></table>
+
+По возможности предоставляйте подсказку ёмкости при инициализации карт с помощью
+`make()`. См. [Указание подсказки ёмкости для
+карт](#указание-подсказки-ёмкости-для-карт) для получения дополнительной
+информации.
+
+С другой стороны, если карта содержит фиксированный список элементов,
+используйте литералы карт для её инициализации.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+m := make(map[T1]T2, 3)
+m[k1] = v1
+m[k2] = v2
+m[k3] = v3
+```
+
+</td><td>
+
+```go
+m := map[T1]T2{
+ k1: v1,
+ k2: v2,
+ k3: v3,
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Основное правило — использовать литералы карт при добавлении фиксированного
+набора элементов во время инициализации, в противном случае используйте `make`
+(и указывайте подсказку размера, если доступна).
+
+### Строки формата вне Printf
+
+Если вы объявляете строки формата для функций в стиле `Printf` вне строкового
+литерала, сделайте их значениями `const`.
+
+Это помогает `go vet` выполнять статический анализ строки формата.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+msg := "unexpected values %v, %v\n"
+fmt.Printf(msg, 1, 2)
+```
+
+</td><td>
+
+```go
+const msg = "unexpected values %v, %v\n"
+fmt.Printf(msg, 1, 2)
+```
+
+</td></tr>
+</tbody></table>
+
+### Именование функций в стиле Printf
+
+Когда вы объявляете функцию в стиле `Printf`, убедитесь, что `go vet` может её
+обнаружить и проверить строку формата.
+
+Это означает, что следует использовать предопределённые имена функций в стиле
+`Printf`, если это возможно. `go vet` проверяет их по умолчанию. См. [Printf
+family](https://pkg.go.dev/cmd/vet#hdr-Printf_family) для получения
+дополнительной информации.
+
+Если использование предопределённых имён невозможно, заканчивайте выбранное вами
+имя на f: `Wrapf`, а не `Wrap`. `go vet` можно попросить проверять определённые
+имена в стиле `Printf`, но они должны заканчиваться на f.
+
+```shell
+go vet -printfuncs=wrapf,statusf
+```
+
+См. также [go vet: Printf family
+check](https://kuzminva.wordpress.com/2017/11/07/go-vet-printf-family-check/).
+
+## Паттерны
+
+### Табличные тесты
+
+Табличные тесты с [подтестами](https://go.dev/blog/subtests) могут быть полезным
+паттерном для написания тестов, чтобы избежать дублирования кода, когда основная
+тестовая логика повторяется.
+
+Если тестируемую систему нужно проверить на соответствие _нескольким условиям_,
+где определённые части входных и выходных данных меняются, следует использовать
+табличные тесты, чтобы уменьшить избыточность и улучшить читаемость.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// func TestSplitHostPort(t *testing.T)
+
+host, port, err := net.SplitHostPort("192.0.2.0:8000")
+require.NoError(t, err)
+assert.Equal(t, "192.0.2.0", host)
+assert.Equal(t, "8000", port)
+
+host, port, err = net.SplitHostPort("192.0.2.0:http")
+require.NoError(t, err)
+assert.Equal(t, "192.0.2.0", host)
+assert.Equal(t, "http", port)
+
+host, port, err = net.SplitHostPort(":8000")
+require.NoError(t, err)
+assert.Equal(t, "", host)
+assert.Equal(t, "8000", port)
+
+host, port, err = net.SplitHostPort("1:8")
+require.NoError(t, err)
+assert.Equal(t, "1", host)
+assert.Equal(t, "8", port)
+```
+
+</td><td>
+
+```go
+// func TestSplitHostPort(t *testing.T)
+
+tests := []struct{
+ give string
+ wantHost string
+ wantPort string
+}{
+ {
+ give: "192.0.2.0:8000",
+ wantHost: "192.0.2.0",
+ wantPort: "8000",
+ },
+ {
+ give: "192.0.2.0:http",
+ wantHost: "192.0.2.0",
+ wantPort: "http",
+ },
+ {
+ give: ":8000",
+ wantHost: "",
+ wantPort: "8000",
+ },
+ {
+ give: "1:8",
+ wantHost: "1",
+ wantPort: "8",
+ },
+}
+
+for _, tt := range tests {
+ t.Run(tt.give, func(t *testing.T) {
+ host, port, err := net.SplitHostPort(tt.give)
+ require.NoError(t, err)
+ assert.Equal(t, tt.wantHost, host)
+ assert.Equal(t, tt.wantPort, port)
+ })
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Табличные тесты облегчают добавление контекста к сообщениям об ошибках,
+уменьшают дублирование логики и позволяют добавлять новые тестовые случаи.
+
+Мы следуем соглашению, что срез структур называется `tests`, а каждый тестовый
+случай — `tt`. Кроме того, мы рекомендуем явно указывать входные и выходные
+значения для каждого тестового случая с префиксами `give` и `want`.
+
+```go
+tests := []struct{
+ give string
+ wantHost string
+ wantPort string
+}{
+ // ...
+}
+
+for _, tt := range tests {
+ // ...
+}
+```
+
+#### Избегайте излишней сложности в табличных тестах
+
+Табличные тесты могут быть трудны для чтения и поддержки, если подтесты содержат
+условные проверки или другую разветвлённую логику. Табличные тесты **НЕ ДОЛЖНЫ**
+использоваться всякий раз, когда внутри подтестов требуется сложная или условная
+логика (т.е. сложная логика внутри цикла `for`).
+
+Большие, сложные табличные тесты ухудшают читаемость и поддерживаемость, потому
+что читателям тестов может быть трудно отлаживать возникающие сбои тестов.
+
+Такие табличные тесты следует разделить либо на несколько таблиц тестов, либо на
+несколько отдельных функций `Test...`.
+
+К некоторым идеалам, к которым стоит стремиться, относятся:
+
+- Фокусировка на самой узкой единице поведения
+- Минимизация «глубины теста» и избегание условных проверок (см. ниже)
+- Обеспечение того, что все поля таблицы используются во всех тестах
+- Обеспечение того, что вся тестовая логика выполняется для всех случаев таблицы
+
+В этом контексте «глубина теста» означает «внутри данного теста, количество
+последовательных проверок, требующих выполнения предыдущих проверок» (аналогично
+цикломатической сложности). Наличие «более мелких» тестов означает меньше связей
+между проверками и, что более важно, что эти проверки по умолчанию менее
+вероятно будут условными.
+
+Конкретно, табличные тесты могут стать запутанными и трудными для чтения, если
+они используют несколько ветвящихся путей (например, `shouldError`, `expectCall`
+и т.д.), используют много операторов `if` для специфичных ожиданий моков
+(например, `shouldCallFoo`) или размещают функции внутри таблицы (например,
+`setupMocks func(*FooMock)`).
+
+Однако при тестировании поведения, которое меняется только в зависимости от
+изменённых входных данных, может быть предпочтительнее группировать схожие
+случаи вместе в табличном тесте, чтобы лучше иллюстрировать, как поведение
+меняется при всех входных данных, а не разделять иначе сопоставимые единицы на
+отдельные тесты и делать их более трудными для сравнения и противопоставления.
+
+Если тело теста короткое и простое, допустимо иметь единственный ветвящийся путь
+для случаев успеха и неудачи с полем таблицы типа `shouldErr` для указания
+ожиданий ошибки.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+func TestComplicatedTable(t *testing.T) {
+ tests := []struct {
+ give string
+ want string
+ wantErr error
+ shouldCallX bool
+ shouldCallY bool
+ giveXResponse string
+ giveXErr error
+ giveYResponse string
+ giveYErr error
+ }{
+ // ...
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.give, func(t *testing.T) {
+ // настройка моков
+ ctrl := gomock.NewController(t)
+ xMock := xmock.NewMockX(ctrl)
+ if tt.shouldCallX {
+ xMock.EXPECT().Call().Return(
+ tt.giveXResponse, tt.giveXErr,
+ )
+ }
+ yMock := ymock.NewMockY(ctrl)
+ if tt.shouldCallY {
+ yMock.EXPECT().Call().Return(
+ tt.giveYResponse, tt.giveYErr,
+ )
+ }
+
+ got, err := DoComplexThing(tt.give, xMock, yMock)
+
+ // проверка результатов
+ if tt.wantErr != nil {
+ require.EqualError(t, err, tt.wantErr)
+ return
+ }
+ require.NoError(t, err)
+ assert.Equal(t, want, got)
+ })
+ }
+}
+```
+
+</td><td>
+
+```go
+func TestShouldCallX(t *testing.T) {
+ // настройка моков
+ ctrl := gomock.NewController(t)
+ xMock := xmock.NewMockX(ctrl)
+ xMock.EXPECT().Call().Return("XResponse", nil)
+
+ yMock := ymock.NewMockY(ctrl)
+
+ got, err := DoComplexThing("inputX", xMock, yMock)
+
+ require.NoError(t, err)
+ assert.Equal(t, "want", got)
+}
+
+func TestShouldCallYAndFail(t *testing.T) {
+ // настройка моков
+ ctrl := gomock.NewController(t)
+ xMock := xmock.NewMockX(ctrl)
+
+ yMock := ymock.NewMockY(ctrl)
+ yMock.EXPECT().Call().Return("YResponse", nil)
+
+ _, err := DoComplexThing("inputY", xMock, yMock)
+ assert.EqualError(t, err, "Y failed")
+}
+```
+
+</td></tr>
+</tbody></table>
+
+Эта сложность делает тест более трудным для изменения, понимания и
+доказательства его корректности.
+
+Хотя строгих правил нет, читаемость и поддерживаемость всегда должны быть
+главными при выборе между табличными тестами и отдельными тестами для
+множественных входов/выходов системы.
+
+#### Параллельные тесты
+
+Параллельные тесты, как и некоторые специализированные циклы (например, те, что
+создают горутины или захватывают ссылки как часть тела цикла), должны заботиться
+о явном присваивании переменных цикла внутри области видимости цикла, чтобы
+гарантировать, что они содержат ожидаемые значения.
+
+```go
+tests := []struct{
+ give string
+ // ...
+}{
+ // ...
+}
+
+for _, tt := range tests {
+ tt := tt // для t.Parallel
+ t.Run(tt.give, func(t *testing.T) {
+ t.Parallel()
+ // ...
+ })
+}
+```
+
+В примере выше мы должны объявить переменную `tt` с областью видимости итерации
+цикла из-за использования `t.Parallel()` ниже. Если мы этого не сделаем,
+большинство или все тесты получат неожиданное значение для `tt` или значение,
+которое меняется во время их выполнения.
+
+<!-- TODO: Объяснить, как использовать _test пакеты. -->
+
+### Функциональные опции
+
+Функциональные опции — это паттерн, в котором вы объявляете непрозрачный тип
+`Option`, который записывает информацию в некоторую внутреннюю структуру. Вы
+принимаете переменное количество этих опций и действуете на основе полной
+информации, записанной опциями во внутренней структуре.
+
+Используйте этот паттерн для необязательных аргументов в конструкторах и других
+публичных API, которые, как вы предвидите, могут потребовать расширения,
+особенно если у вас уже есть три или более аргументов в этих функциях.
+
+<table>
+<thead><tr><th>Плохо</th><th>Хорошо</th></tr></thead>
+<tbody>
+<tr><td>
+
+```go
+// package db
+
+func Open(
+ addr string,
+ cache bool,
+ logger *zap.Logger
+) (*Connection, error) {
+ // ...
+}
+```
+
+</td><td>
+
+```go
+// package db
+
+type Option interface {
+ // ...
+}
+
+func WithCache(c bool) Option {
+ // ...
+}
+
+func WithLogger(log *zap.Logger) Option {
+ // ...
+}
+
+// Open создаёт соединение.
+func Open(
+ addr string,
+ opts ...Option,
+) (*Connection, error) {
+ // ...
+}
+```
+
+</td></tr>
+<tr><td>
+
+Параметры cache и logger всегда должны предоставляться, даже если пользователь
+хочет использовать значения по умолчанию.
+
+```go
+db.Open(addr, db.DefaultCache, zap.NewNop())
+db.Open(addr, db.DefaultCache, log)
+db.Open(addr, false /* cache */, zap.NewNop())
+db.Open(addr, false /* cache */, log)
+```
+
+</td><td>
+
+Опции предоставляются только при необходимости.
+
+```go
+db.Open(addr)
+db.Open(addr, db.WithLogger(log))
+db.Open(addr, db.WithCache(false))
+db.Open(
+ addr,
+ db.WithCache(false),
+ db.WithLogger(log),
+)
+```
+
+</td></tr>
+</tbody></table>
+
+Наш рекомендуемый способ реализации этого паттерна — использование интерфейса
+`Option` с неэкспортируемым методом, записывающим опции в неэкспортируемую
+структуру `options`.
+
+```go
+type options struct {
+ cache bool
+ logger *zap.Logger
+}
+
+type Option interface {
+ apply(*options)
+}
+
+type cacheOption bool
+
+func (c cacheOption) apply(opts *options) {
+ opts.cache = bool(c)
+}
+
+func WithCache(c bool) Option {
+ return cacheOption(c)
+}
+
+type loggerOption struct {
+ Log *zap.Logger
+}
+
+func (l loggerOption) apply(opts *options) {
+ opts.logger = l.Log
+}
+
+func WithLogger(log *zap.Logger) Option {
+ return loggerOption{Log: log}
+}
+
+// Open создаёт соединение.
+func Open(
+ addr string,
+ opts ...Option,
+) (*Connection, error) {
+ options := options{
+ cache: defaultCache,
+ logger: zap.NewNop(),
+ }
+
+ for _, o := range opts {
+ o.apply(&options)
+ }
+
+ // ...
+}
+```
+
+Обратите внимание, что существует метод реализации этого паттерна с
+использованием замыканий, но мы считаем, что паттерн выше предоставляет больше
+гибкости авторам и легче отлаживается и тестируется пользователями. В частности,
+он позволяет сравнивать опции друг с другом в тестах и моках, в отличие от
+замыканий, где это невозможно. Кроме того, он позволяет опциям реализовывать
+другие интерфейсы, включая `fmt.Stringer`, что позволяет создавать удобочитаемые
+строковые представления опций.
+
+См. также,
+
+- [Self-referential functions and the design of
+ options](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html)
+- [Functional options for friendly
+ APIs](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
+
+<!-- TODO: заменить это на структуры параметров и функциональные опции, когда использовать одно против другого -->
+
+## Линтинг
+
+Более важно, чем любой «благословленный» набор линтеров, — линтить
+последовательно по всей кодовой базе.
+
+Мы рекомендуем использовать следующие линтеры как минимум, потому что считаем,
+что они помогают выявить наиболее распространённые проблемы, а также
+устанавливают высокую планку качества кода, не будучи излишне предписывающими:
+
+- [errcheck](https://github.com/kisielk/errcheck) для обеспечения обработки
+ ошибок
+- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) для
+ форматирования кода и управления импортами
+- [golint](https://github.com/golang/lint) для указания на распространённые
+ стилевые ошибки
+- [govet](https://pkg.go.dev/cmd/vet) для анализа кода на распространённые
+ ошибки
+- [staticcheck](https://staticcheck.dev) для выполнения различных проверок
+ статического анализа
+
+### Запускатели линтеров
+
+Мы рекомендуем [golangci-lint](https://github.com/golangci/golangci-lint) в
+качестве основного запускателя линтеров для кода на Go, во многом благодаря его
+производительности в больших кодовых базах и возможности настраивать и
+использовать многие канонические линтеры одновременно. Этот репозиторий содержит
+пример
+[.golangci.yml](https://github.com/uber-go/guide/blob/master/.golangci.yml)
+файла конфигурации с рекомендуемыми линтерами и настройками.
+
+golangci-lint имеет [различные
+линтеры](https://golangci-lint.run/usage/linters/), доступные для использования.
+Вышеуказанные линтеры рекомендуются в качестве базового набора, и мы поощряем
+команды добавлять любые дополнительные линтеры, которые имеют смысл для их
+проектов.
diff --git a/content/pages/setup/_index.md b/content/pages/setup/_index.md
new file mode 100644
index 0000000..92c00a3
--- /dev/null
+++ b/content/pages/setup/_index.md
@@ -0,0 +1,6 @@
+---
+order: 10
+title: Мой сетап 2025
+---
+
+Пополняемый раздел с моим сетапом
diff --git a/content/pages/setup/laptop.md b/content/pages/setup/laptop.md
new file mode 100644
index 0000000..4aa14c9
--- /dev/null
+++ b/content/pages/setup/laptop.md
@@ -0,0 +1,36 @@
+---
+order: 20
+title: Ноутбук
+---
+
+Уже достаточно давно я сторонник исключительно ноутбуков и никак не воспринимаю
+стационарные компьютеры. При этом, я считаю, что ноутбук должен быть
+одновременно и мощным, мобильным и, что важно, ремонтопригодным, даже в домашних
+условиях. Понимаю, что на практике это практически не осуществимо. Но самым
+близким к этому для меня стал Lenovo Thinkpad T14 Gen4, версия на intel (это
+важно, т.к. только intel версия поддерживает расширение ОЗУ). ОЗУ я в нем добил
+до 48Гб, пока мне хватает.
+
+Вот основное что на нём установлено:
+
+- ОС: AltLinux p11.
+- DE: Gnome 48
+- Эмулятор терминала: Ghostty.
+- Оболочка: zsh.
+- Текстовый редактор: Neovim/VSCodium
+- Браузер: Яндекс Браузер.
+- Коммуникации: Thunderbird/Neomutt (e-mail), Dino (jabber), telegram desktop
+ (будь он неладен).
+- Музыка: Rhythmbox.
+- Видео: VLC.
+- Книги: лежат на NAS + сразу достаточно много загрузил в читалку. Была calibre,
+ но я так и не оценил от неё плюсов и дропнул.
+- Основной язык: golang (удивительно).
+- Синхронизация: с помощью syncthing синхронизирую ноутбук <-> NAS <-> смартфон
+ только одну директорию: Документы. По сути, не считая директории с исходниками
+ проектов, это моя самая важная директория на компьютере.
+- Хранилище знаний: <del>[Obsidian](/posts/2024-11-17-obsidian/)</del> пока
+ присматриваюсь к ZK или к обычным текстовым файлам
+
+Это база, остальное не столько важно.
+
diff --git a/content/pages/setup/nas.md b/content/pages/setup/nas.md
new file mode 100644
index 0000000..a3f3d25
--- /dev/null
+++ b/content/pages/setup/nas.md
@@ -0,0 +1,32 @@
+---
+order: 40
+title: NAS
+---
+
+Synology DS420+
+
+Это, наверное, одно из лучших моих вложений денег. Мой личный суверенитет от
+облаков.
+
+Основные функции:
+
+* Собственно, синхронизация той самой важной для меня директории «Документы».
+ Потому что её потерю я точно не смогу восполнить.
+* Инкрементальный бекап ноутбука через rsync, как я где-то уже писал, вроде в
+ телеге (может стоит и здесь оставить заметку на всякий случай?).
+* Календарь / контакты по webdav. Просто приятно. Хорошая альтернатива облакам.
+* Торрентокачалка. Пиратство морально оправдано. Точка.
+* Медиасервер. В основном чтобы просматривать трофейный контент на смарт тв.
+* TT-RSS. Читаю RSS ленты на смартфоне и ноутбуке, а TT-RSS позволяет
+ синхронизировать уже прочитанное.
+
+Утерянный или недоделанный функционал:
+
+* Синхронизация подкастов по протоколу gpodder (AntennaPod на смартфоне и в
+ автомобиле + Kasts на ноутбуке). Раньше синкал через Nextcloud, отказался от
+ него. Пилю синхронизацию в свободное время. Когда допилю — расскажу в этом
+ блоге.
+* Медиастримминг музыки с NAS на смартфон / компьютер / автомобиль. Пока не
+ дошли руки наладить. Обхожусь дедовскими способами — на каждом устройстве
+ просто локальная медиатека.
+
diff --git a/content/pages/setup/pda.md b/content/pages/setup/pda.md
new file mode 100644
index 0000000..59aa33c
--- /dev/null
+++ b/content/pages/setup/pda.md
@@ -0,0 +1,38 @@
+---
+order: 30
+title: Смартфон
+---
+OnePlus 10T (рутованный)
+
+Бо́льшая часть гуглоговна удалена или отключена. В основном, стараюсь
+использовать софт из F-Droid.
+
+В основном, стараюсь держать минимально необходимый набор софта — банковские
+приложения, навигацию, коммуникационные приложения, читалки, да пару простеньких
+игрушек для скрашивания досуга.
+
+Самое главное — по максимуму отключаю всевозможные уведомления. Ничего не должно
+меня тревожить. Всё так же остаюсь сторонником идеи что пуши не нужны, а что-то
+_действительно_ важное — и так придёт на e-mail. Единственное исключение —
+jabber и рабочий мессенджер. Первый — это этакий бонус узкому элитарному кругу
+пользователей джаббера, а второй — поскольку рабочие обязанности важнее моих
+заморочек.
+
+Из интересного:
+
+* ntodotxt - тудушник, который отлично работает с Todo.txt.
+* Conversations - jabber.
+* syncthing - синхронизация.
+* DAVx5 - приложение для синхронизации календарей и контактов с NAS.
+* DS File, DS Get - приложения для работы с NAS.
+* OSMand и OrganicMaps - оффлайн навигация. Ни раз выручала, когда онлайновый
+ карты приказывали долго жить без интернетов.
+* AntennaPod - ИМХО лучшее приложение для подкастов.
+* AIMP - замечательный аудио плеер для локальной музыки.
+* VLC - отличный видеоплеер.
+* KDE Connect - стоит исключительно для быстрого перебрасывания файлов на
+ компьютер.
+
+Вроде, из самого интересного — всё. Буду дописывать, если что ещё вспомню.
+
+
diff --git a/content/posts/2021-02-13-jsonnet.md b/content/posts/2021-02-13-jsonnet.md
new file mode 100644
index 0000000..e4ea27e
--- /dev/null
+++ b/content/posts/2021-02-13-jsonnet.md
@@ -0,0 +1,101 @@
+---
+categories:
+- Без рубрики
+date: '2021-02-13T22:08:19Z'
+image: files/2021-02-13-jsonnet_logo.webp
+tags:
+- go
+- it
+- разное
+title: Jsonnet
+---
+
+Редко такое бывает, что случайно натыкаешься на какую-то технологию и она
+вызывает вау-эффект и буквально переворачивает всё верх дном. На днях для меня
+такой технологией стал [Jsonnet](https://jsonnet.org/) от Google.
+
+В кратце, это надмножество JSON являющееся языком описания шаблонов. Пока звучит
+не очень круто, да? На деле это офигенный Тьюринг полный функциональный язык,
+результатом выполнения которого будет сформированый JSON (и не только)
+документ(или несколько документов[^1]).
+[^1]:https://jsonnet.org/learning/getting_started.html#multi
+
+Если интересно, рекомендую сразу переходить к туториалу —
+https://jsonnet.org/learning/tutorial.html.
+
+## Почему же это круто?
+
+Ну, во-первых, он реально мощный и простой. С его помощью можно формировать
+документы любой сложности.
+
+Во-вторых, его можно встроить в свою программу на Go (и не только, но на Go —
+проще всего — https://jsonnet.org/ref/bindings.html), и это даст бесплатно
+мощный DSL для написания очень гибких конфигов.
+
+В третьих, ну камон, приятно же когда компьютер берет на себя рутинную работу по
+формированию больших и сложных JSON’ов!
+
+## Пример
+
+Накидал простенький пример который формирует конфигурацию пайплайна для
+гипотетической CI системы:
+
+```json
+local map(arr, predicate) = // определяем функцию map
+if std.length(arr) == 0 then
+ []
+ else
+ [
+ predicate(arr[0])
+ ] + map(arr[1:], predicate); // функциональненько!
+local tasks = [['go1.14', '1.14-alpine'],['go1.15', '1.15-alpine'],['go1.16-RC', '1.16-rc-alpine']];
+local commands = ['go build', 'go test']; // Общая часть
+{ // Результирующий JSON
+ pipeline: map(tasks, function (task) { // Вызов map от tasks
+ name: task[0],
+ image: "golang:"+task[1],
+ commands: commands,
+ })
+}
+```
+
+Результат:
+
+```json
+{
+ "pipeline": [
+ {
+ "commands": [
+ "go build",
+ "go test"
+ ],
+ "image": "golang:1.14-alpine",
+ "name": "go1.14"
+ },
+ {
+ "commands": [
+ "go build",
+ "go test"
+ ],
+ "image": "golang:1.15-alpine",
+ "name": "go1.15"
+ },
+ {
+ "commands": [
+ "go build",
+ "go test"
+ ],
+ "image": "golang:1.16-rc-alpine",
+ "name": "go1.16-RC"
+ }
+ ]
+}
+```
+
+Круть же!
+
+Да, на небольшом примере не очень показательно, но даже тут, скажем, при добавлении новой цели сборки будет достаточно слегка подправить массив tasks и автоматически сформируется все остальное, а не копипаст целой секции и ручная правка в нужных местах.
+
+Я оставил за скобками то, что этот шаблонизатора позволяет формировать не только JSON но и фактически любой другой текстовый формат. И даже из одного скрипта формировать несколько документов разного формата. При этом локальные переменные будут использоваться общие. Теоретически, если упороться, можно одним скриптом сформировать весь /etc на новом сервере. Почему бы и нет?:)
+
+Не знаю смог ли передать ощущение своего восторга, но я охренеть как рад и жду выходных, чтобы с головой нырнуть в эту технологию, которая открывает столько новых интересных перспектив! \ No newline at end of file
diff --git a/content/posts/2021-05-13-цифровая-гигиена.md b/content/posts/2021-05-13-цифровая-гигиена.md
new file mode 100644
index 0000000..d9fcede
--- /dev/null
+++ b/content/posts/2021-05-13-цифровая-гигиена.md
@@ -0,0 +1,91 @@
+---
+categories:
+- Без рубрики
+date: '2021-05-13T15:37:01Z'
+tags:
+- it
+- паранойя
+- разное
+title: Немного о цифровой гигиене
+---
+
+## Вступление
+
+Как раз вступление тут особо и не нужно. Ни для кого не открою америки, что в
+современном цифровом обществе все мы являемся товаром для интернет-медиа
+гигантов, того же фейсбука да гугла. Не скажу что это для меня, как личности
+опасно или вредно, но мне это неприятно. Решил с этим что-то делать.
+
+## Вводные
+
+- В интернетах я уже очень давно и много где и как “наследил” своими данными. И
+ с этим уже ничего не поделать.
+- У меня в телефоне и на всех компьютерах куча приложений работающих с
+ интернетом, и не только мессенджеры.
+- У меня достаточно узкий круг людей с кем бы я хотел быть на связи, и не хочу
+ чтобы мои действия как-то ухудшили или усложнили их жизнь.
+- Вопрос анонимности для меня не стоит, я не анонимен и это моё осознанное
+ решение. Я законопослушный человек и прятаться мне не от кого. И да, я знаю
+ что этот тезис стараниями либерах нынче пытаются выставить как глупость, но
+ нет. Глупость — это слушать либерах, а не иметь свою голову на плечах. И
+ контртезис “Гы гы гы, ну раз тебе нечего скрывать — поставь камеру у себя в
+ спальне и ванной” даже комментировать не буду в силу его ущербности.
+- Я пользователь техники Apple и с этим уже ничего не поделать, менять целиком
+ экосистему для меня не вариант (это очень дорого, бессмысленно, а местами и
+ невозможно, например, рабочий мак мне поменять не на что). И да, есть наивная
+ надежда что у яблок в плане приватности всё получше чем у ведроидов. Во всяком
+ случае по сравнению со стоком. Гиковские прошивки с вырезанными зондами в
+ расчет не беру, верю что у них совсем всё хорошо.
+
+## Цели
+
+1. Уменьшить информационный шум вокруг себя и тем самым улучшить качество жизни.
+2. Уменьшить свой “информационный след”
+3. Иметь больше контроля над своими данными, чтобы мои волосы стали мягкими и
+ шелковистыми.
+
+## Наброски плана
+
+1. Перейти максимально на собственные ресурсы, которые я контролирую и которые
+ *точно* не сливают ничего налево.Примерно так: социалки =&gt;
+ <https://soc.neonxp.ru/> , GitHub =&gt; <https://gitrepo.ru/> , Облачные
+ диски =&gt; локальный NAS Synology и т.д.
+2. Мне надо сократить мессенджеры в идеале до одного, не считая корпоративного
+ рабочего. Тут всё просто — оставляю Telegram, остальные сношу.
+3. Мне надо отказаться от неэтичных социальных сетей, где я не могу полностью
+ контролировать свои данные.
+4. При отказе от социальных сетей чтобы не доставить проблем моим контактам надо
+ оставить “новый адрес” по которому со мной можно связаться и, например, этот
+ пост. Я не хочу чтобы для всех мои действия были прозрачны и понятны, а не
+ “молча удалиться” оставив кого-то в недоумении.
+5. Везде где возможно отключить или заблокировать телеметрию, чтобы как можно
+ меньше моих данных неконтролируемо утекало. Да, полностью не перекрыть, но
+ сократить возможно.
+6. Для связи с “миром” оставить только e-mail как наиболее удобный асинхронный
+ метод коммуникации.
+
+## Дальнейшие шаги
+
+1. Превратить наброски плана в цельный план. Написать манифест? Возможно.
+2. Подготовить “визитку” с актуальными контактами и объяснением что произошло.
+ Причем как в виде изображения, так и текста.
+3. Вышеуказанную визитку поместить на уже неактуальных для меня местах обитания
+ (инстаграм, вк и проч). Удаляться не хочу. Жалко контент за столько лет, да и
+ пункт 3 предыдущего абзаца.
+4. Удалить “лишние” приложения от вышеуказанных сервисов.
+5. Разлогиниться в этих сервисах и очистить браузеры от них, чтобы исключить
+ треккинг на сторонних сайтах.
+6. ?????
+7. PROFIT!
+
+## Обратная связь
+
+Очень бы хотелось получить обратную связь по моему плану. Комментарии про то что
+упустил и предложения улучшений приветствуются в комментариях к посту, в
+комментариях к [телеграм каналу](https://t.me/neonxp), или на почту
+<a.kiryukhin@mail.ru> (кстати, стоит наверное и почту перевести к себе? Но пока
+уровень сервиса врядли смогу адекватный обеспечить)
+
+*UPD:* Да, я знаю что уже данные так и останутся в чужих руках, но со временем
+они будут всё больше и больше протухать, а мой “цифровой профиль” терять
+актуальность. Ведь я не скала, я тоже меняюсь и ухожу от этого профиля. \ No newline at end of file
diff --git a/content/posts/2022-05-30-возрождение.md b/content/posts/2022-05-30-возрождение.md
new file mode 100644
index 0000000..c6721c8
--- /dev/null
+++ b/content/posts/2022-05-30-возрождение.md
@@ -0,0 +1,27 @@
+---
+categories:
+- Без рубрики
+date: '2022-05-30T23:37:00Z'
+tags:
+- блог
+- моё
+title: Возрождение?
+---
+
+Определенно, вести блог это не мое. Учитывая, что последний пост был год назад —
+sad but true.
+
+Не буду говорить, что “вот сейчас то уж точно буду вести регулярно”. Нет не
+буду.
+
+Но раз в полгода-год, наверное все же буду.
+
+Из новостей, что не писал в канал, наверное, только парочка:
+
+1. сейчас всё свободное время пилю свой петпроджект 😉 Пока что выходит ух какая
+ красота. Но об этом как-нибудь в другой раз, как говорится, пол работы не
+ показывают 🙂
+2. в ленивом режиме начали заниматься вопросами улучшения жилищных условий.
+ Давно пора.
+
+До встречи когда-нибудь потом 🙂 \ No newline at end of file
diff --git a/content/posts/2022-05-31-golang-1.md b/content/posts/2022-05-31-golang-1.md
new file mode 100644
index 0000000..75e1b64
--- /dev/null
+++ b/content/posts/2022-05-31-golang-1.md
@@ -0,0 +1,44 @@
+---
+categories:
+- Без рубрики
+date: '2022-05-31T01:00:00Z'
+tags:
+- go
+- it
+title: Golang подборка 1
+---
+
+Просто собираю подборку интересных ссылок по гошке на почитать потом.
+
+- [Extra](https://github.com/neonxp/extra) — Моё. Пакет с разными полезными
+ функциями без дополнительных зависимостей.
+- Серия видосов про создание игры в стиле Animal Crossing на golang с помощью
+ raylib —
+ https://www.youtube.com/watch?v=iWp-mCIQgMU&list=PLVotA8ycjnCsy30WQCwVU5RrZkt4lLgY5&index=1
+- Самописный распределенный типа Postgres
+ https://notes.eatonphil.com/distributed-postgres.html. Под капотом raft от
+ hashicorp, boltdb и самое интересное — парсинг SQL
+- Рассчет расстояния между двумя Geo точками:
+
+```go
+import "math"
+...
+// https://en.wikipedia.org/wiki/Haversine_formula
+func GetDistance(lat1, lon1, lat2, lon2 float64) float64 {
+ lat1 *= math.Pi / 180
+ lon1 *= math.Pi / 180
+ lat2 *= math.Pi / 180
+ lon2 *= math.Pi / 180
+ return 12742 * math.Asin(
+ math.Sqrt(
+ math.Pow(math.Sin((lat2-lat1)/2), 2) +
+ math.Cos(lat1) *
+ math.Cos(lat2) *
+ math.Pow(math.Sin((lon2-lon1)/2), 2)
+ )
+ )
+}
+```
+
+- [god](https://github.com/pioz/god) — Утилита подгатавливающая демоны из go
+ программы. Для меня ценное — что генерит systemd конфиги. \ No newline at end of file
diff --git a/content/posts/2023-01-12-gitrepo.md b/content/posts/2023-01-12-gitrepo.md
new file mode 100644
index 0000000..7e521c3
--- /dev/null
+++ b/content/posts/2023-01-12-gitrepo.md
@@ -0,0 +1,167 @@
+---
+categories:
+- Мои проекты
+date: '2023-01-12T20:22:00Z'
+tags:
+- it
+- моё
+title: GitRepo.ru
+---
+
+Сегодня серьезно переделал свой хостинг [репозиториев
+кода](https://gitrepo.ru/):
+
+- Переехал на большой арендованный сервак
+- Привел в порядок оркестрацию вокруг сервака с использованием Docker Compose
+- Gitea заменил на её форк [Forgejo](https://forgejo.org/)
+- Впилил CI/CD на основе [Woodpecker CI](https://woodpecker-ci.org/)
+
+Приглашаю пользоваться заместо бездуховного западного github:
+<https://gitrepo.ru/>
+
+Сервер физически находится в датацентре в Москве у весьма годного провайдера
+Selectel.
+
+Тем более, время сейчас неспокойное и неизвестно когда github станет недоступен
+для РФ, а GitRepo — он вот тут, в нашей стране.
+
+# Немного про устройство
+
+Расскажу немного как я организовал себе Ops сервиса.
+
+У меня на руках `docker-compose.yml` который полностью описывает всю
+конфигурацию сервака, примерно так:
+
+```yml
+version: "3"
+services:
+ caddy:
+ image: caddy:2.6.2-alpine
+ container_name: gateway
+ restart: unless-stopped
+ ports:
+ - "80:80"
+ - "443:443"
+ - "443:443/udp"
+ volumes:
+ - ./Caddyfile:/etc/caddy/Caddyfile
+ - caddy_data:/data
+ - caddy_config:/config
+ networks:
+ - gateway
+ git:
+ image: codeberg.org/forgejo/forgejo:1.18.0-1
+ container_name: git
+ environment:
+ - USER_UID=1000
+ - USER_GID=1000
+ - TZ=Europe/Moscow
+ - USER=git
+ - GITEA__database__DB_TYPE=postgres
+ - GITEA__database__HOST=db:5432
+ - GITEA__database__NAME=${PG_NAME}
+ - GITEA__database__USER=${PG_USER}
+ - GITEA__database__PASSWD=${PG_PASS}
+ restart: always
+ networks:
+ - gitea
+ - gateway
+ volumes:
+ - /home/git/.ssh/:/data/git/.ssh
+ - forgejo:/data
+ - /etc/timezone:/etc/timezone:ro
+ - /etc/localtime:/etc/localtime:ro
+ ports:
+ - "127.0.0.1:2222:22"
+ depends_on:
+ db:
+ condition: service_healthy
+ db:
+ image: postgres:13
+ restart: always
+ environment:
+ - POSTGRES_USER=${PG_USER}
+ - POSTGRES_PASSWORD=${PG_PASS}
+ - POSTGRES_DB=${PG_NAME}
+ healthcheck:
+ test: /usr/bin/pg_isready
+ interval: 5s
+ timeout: 10s
+ retries: 120
+ networks:
+ - gitea
+ volumes:
+ - postgres:/var/lib/postgresql/data
+ woodpecker-server:
+ image: woodpeckerci/woodpecker-server:latest
+ volumes:
+ - woodpecker-server-data:/var/lib/woodpecker/
+ environment:
+ - WOODPECKER_OPEN=true
+ - WOODPECKER_GITEA=true
+ - WOODPECKER_GITEA_URL=https://gitrepo.ru
+ - WOODPECKER_GITEA_CLIENT=${GITEA_CLIENT}
+ - WOODPECKER_GITEA_SECRET=${GITEA_SECRET}
+ - WOODPECKER_HOST=https://ci.gitrepo.ru
+ - WOODPECKER_ADMIN=neonxp
+ - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
+ networks:
+ - gitea
+ - gateway
+ depends_on:
+ - git
+ woodpecker-agent:
+ image: woodpeckerci/woodpecker-agent:latest
+ command: agent
+ restart: always
+ depends_on:
+ - woodpecker-server
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ environment:
+ - WOODPECKER_SERVER=woodpecker-server:9000
+ - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
+ networks:
+ - gitea
+volumes:
+ woodpecker-server-data:
+ caddy_data:
+ caddy_config:
+ forgejo:
+ postgres:
+networks:
+ gateway:
+ gitea:
+ external: false
+
+```
+
+а рядом лежит `.env` файлик с значениями переменных `${...}`.
+
+Запускаю деплой я с локального компьютера, предварительно добавив удаленный
+сервер в [контекст
+докера](https://docs.docker.com/engine/context/working-with-contexts/):
+
+```
+# Создаю новый контекст для удаленного сервера
+docker context create gitrepo --docker "host=ssh://gitrepo.ru"
+# Все последующие docker команды выполняются на удаленном сервере
+docker use gitrepo
+# Возвращаюсь в локальный контекст
+docker use default
+```
+
+# Оставшиеся проблемы
+
+Сейчас так получается, что Caddyfile должен лежать на удаленном сервере, т.к.
+часть конфига
+
+```yml
+ volumes:
+ - ./Caddyfile:/etc/caddy/Caddyfile
+```
+
+выполняется в контексте именно удаленного сервера, а значит при его апдейте на
+локальном серваке приходится делать SCP этого файла на сервак. Такое себе.
+
+Как это решить — есть интересная идея, но это уже в другой раз. \ No newline at end of file
diff --git a/content/posts/2023-05-26-gist.md b/content/posts/2023-05-26-gist.md
new file mode 100644
index 0000000..25b362f
--- /dev/null
+++ b/content/posts/2023-05-26-gist.md
@@ -0,0 +1,19 @@
+---
+categories:
+- Мои проекты
+date: '2023-05-26T17:40:21Z'
+tags:
+- it
+- моё
+title: Импортозамещение Gist
+---
+
+
+И в догонку к комментариям, запустил на своём серваке свой аналог Gist’ов от
+GitHub.
+
+Вот и он: [gist.neonxp.ru](https://gist.neonxp.ru/)
+
+Пользуйтесь 🙂
+
+[Другие мои проекты](/projects) \ No newline at end of file
diff --git a/content/posts/2023-07-24-tls.md b/content/posts/2023-07-24-tls.md
new file mode 100644
index 0000000..43ee345
--- /dev/null
+++ b/content/posts/2023-07-24-tls.md
@@ -0,0 +1,85 @@
+---
+categories:
+- Без рубрики
+date: '2023-07-24T20:04:17Z'
+tags:
+- it
+- Россия
+- TLS
+title: Немного мыслей о TLS (HTTPS) в России
+---
+
+Накопилось немного мыслей относительно того, что может грозить нам (и мне) в
+связи с трендом на “балканизацию” рунета.
+
+И самое болезненное место — HTTPS который нынче стандарт де-факто в современных
+интернетах. А болезненное оно потому, что целиком и полностью контролируется
+другой стороной нынешного противостояния. Все доверенные удостоверяющие центры
+принадлежат странам “коллективного запада”. Помню, были ещё какие-то китайские,
+вроде, но с ними был какой-то скандал и не факт что они есть.
+
+Есть относительно [доверенный УЦ от Минцифры](https://www.gosuslugi.ru/tls). Это
+здорово и я это всецело поддерживаю. Вот только есть момент. Он не для нас,
+простых людей, и при попытке его получить видим то, что на скриншоте ниже. А
+сранный Firefox вообще хочет его внести в черный список, чтобы даже специально
+его нельзя было установить. В общем, пока его я поставить не могу даже при всём
+желании.
+
+Какие ещё альтернативы есть, если нас вдруг прокинет Let’s encrypt?
+
+1. Не использовать HTTPS вообще. Я же не магазин и у меня нет форм логина,
+ которые требуют шифрования. Так-то оно так, да не так. Браузеры уже сейчас
+ очень косо смотрят на “обычные”, не HTTPS сайты, а в дальнейшем, не удивлюсь
+ если перестанут открывать вообще. Так же на HTTP сайтах не работают
+ прикольные браузерные API типа геолокации (наверное, это в каком-то роде даже
+ плюс 😉 ). Ну и ещё проблема, что, например, этот сайт без HTTPS вообще не
+ может работать, ибо для доменов зоны .dev насильно включено HSTS и они не
+ могут работать не по HTTPS. Последнее то я решу старым добрым доменом
+ neonxp.ru, но тем не менее.
+2. Самоподписанные сертификаты. Вот это уже более менее похоже на правду! Да,
+ такие сайты надо добавлять в исключения и мороки с сертификатами чуть больше.
+ Но тут та же история с доменами .dev. Для них самоподписаные не катят. Выход
+ — опять таки старый добрый neonxp.ru.
+
+К чему я всё это? А то что в случае “балканизации” мы остаемся без нормального
+валидного HTTPS. Для себя я выбрал второй путь, с самоподписанными
+сертификатами. Чекнуть как работает можно на зеркале блога на
+<https://neonxp.ru> . Там я выпустил сам себе сертификат на домен от своего
+собственного удостоверяющего центра 🙂 А доверять ему или не доверять — дело
+посетителей сайта.
+
+Если доверяете мне то [вот сертификат моего УЦ](/files/root_ca.crt), а установка
+такая же как сертификата Минцифры 🙂
+
+Ну и совсем краткая инструкция как выпустить сертификат для себя:
+
+1. `openssl genrsa -out root_ca.key 4096` — создание секретного ключа УЦ (должен
+ храниться в безопасности!)
+2. `openssl req -x509 -new -key root_ca.key -days 3650 -out root_ca.crt` —
+ создаем сам сертификат УЦ (он НЕ секретный). Я указал срок действия 10 лет,
+ но это потому что я ленивый и не хочу его перегенеривать каждый год. Так
+ делать не советую.
+3. `openssl genrsa -out server.key 4096` — создаем секретный ключ уже для
+ конкретного сайта (и поддоменов)
+4. `openssl req -new -key server.key -subj "/CN=neonxp.ru/CN=*.neonxp.ru" -out
+ server.csr` — генерируем файл запроса для конкретного сайта
+5. Создаем файл `openssl.cnf` с примерно таким содержимым:
+ ```
+ [SAN]
+ subjectAltName = @alt_names
+ [alt_names]
+ DNS.1 = neonxp.ru
+ DNS.2 = *.neonxp.ru
+ ```
+6. И, наконец, создаем сертификат для сайта, который будет подписан ключами
+ server.key и root\_ca.key (то есть и своим удостоверяющим центром тоже):
+ ```
+ openssl x509 -req -in server.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out server.crt -days 365 -extensions SAN -extfile openssl.cnf
+ ```
+
+В общем, всё. Полученные root_ca.crt (но не root_ca.key!), server.key и
+server.crt можно вносить в конфигурацию используемого вебсервера. А так же
+внести root_ca.crt в доверенные для себя.
+
+Так у меня выглядят [сертификат на сайт](/img/posts/20230724_204209.webp) и
+[сертификат УЦ](/img/posts/20230724_204325.webp). \ No newline at end of file
diff --git a/content/posts/2023-12-29-переезд.md b/content/posts/2023-12-29-переезд.md
new file mode 100644
index 0000000..c4a17bf
--- /dev/null
+++ b/content/posts/2023-12-29-переезд.md
@@ -0,0 +1,28 @@
+---
+categories:
+- Без рубрики
+date: '2023-12-29T00:15:44Z'
+tags:
+- блог
+- разное
+title: Переезд и проблемы обновления
+---
+
+Немного новостей.
+
+Начну с грустного. Крайне неудачно обновил forgejo на gitrepo.ru. В общем, БД
+побилась без возможности восстановления. Репозитории я спас, обращайтесь —пришлю
+архив репозиториев.
+
+Очень грустно, я был крайне расстроен. Штош, теперь настроил зато постоянные
+бекапы БД и данных на локальный NAS. Прошу прощения у пользователей, я очень
+виноват.
+
+А теперь о негрустном. Всё же решил что мне больше нравится основным домен не
+.dev, а именно .ru. Времена неспокойные — лучше перестраховаться и сделать
+ставку именно на национальный домен, а не на международный. К тому же у .dev
+домена есть неприятная особенность, что он требует обязательно валидного (то
+есть одобренного западными “партнерами”) сертификата. А это не дело, как я уже
+[писал в заметке](https://neonxp.ru/posts/2023-07-24-tls/).
+
+Пока что как-то так 🤷🏻‍♂️ \ No newline at end of file
diff --git a/content/posts/2024-01-03-архив.md b/content/posts/2024-01-03-архив.md
new file mode 100644
index 0000000..65a5be8
--- /dev/null
+++ b/content/posts/2024-01-03-архив.md
@@ -0,0 +1,14 @@
+---
+categories:
+- Без рубрики
+date: '2024-01-03T17:28:40Z'
+tags:
+- блог
+title: Архив
+---
+
+Покопавшись по вебархиву смог вытащить древние посты с разных моих старых
+блогов. В основном, кринжовые, конечно, но это моя жизнь, как она была в то
+время. Так что пусть будут.
+
+[Архив блога](https://neonxp.ru/archive/) \ No newline at end of file
diff --git a/content/posts/2024-02-21-tls.md b/content/posts/2024-02-21-tls.md
new file mode 100644
index 0000000..65f8dfa
--- /dev/null
+++ b/content/posts/2024-02-21-tls.md
@@ -0,0 +1,54 @@
+---
+categories:
+- Без рубрики
+date: '2024-02-21T21:51:29Z'
+tags:
+- it
+- Россия
+- TLS
+title: Конфигурация HTTPS с сертификатом от Минцифры
+---
+
+Третьего дня потратил достаточно много времени на установку на данном сайте
+сертификата от Минцифры.А поскольку сертификат краткоживущий (90 дней) — заметка
+мне самому пригодится на будущее.
+
+Началось всё с того, что я с удивлением обнаружил, что на госуслугах теперь
+можно выпустить сертификат для домена физлицу.Это меня обрадовало, хотя ранее я
+приунывал что нет никакой альтернативы простым смертным. Теперь есть.
+<del>Закрывайте буржуйнет.</del>
+
+Поехали!
+
+1. Идём сюда: https://www.gosuslugi.ru/627603/1/form
+2. По приведенной инструкции генерируем файл запроса сертификата. Вкратце так (только вместо neonxp.ru указываем свой домен):
+ ```
+ openssl req -out neonxp.ru.csr -new -subj "/C=RU/CN=neonxp.ru" -addext "keyUsage = digitalSignature, keyEncipherment" -addext "subjectAltName=DNS: neonxp.ru" -addext "extendedKeyUsage = serverAuth" -newkey rsa:2048 -nodes -keyout neonxp.ru.key
+ ```
+ Важно! Нужно сохранить файл ключа neonxp.ru.key в надежном месте. Если он попадет в чужие руки — нужно будет отзывать сертификат и начинать всё заново! SAN и Wildcard пока не поддерживается, но что имеем — то и имеем. Но по слухам таки будут, как минимум SAN.
+3. Полученный файл csr загружаем там же на госуслуги
+4. Ждём не долго (реально недолго, у меня прислали сертификат буквально через несколько минут!)
+5. В ответ придёт файл с рандомным названием. Сохраняем его туда, где лежат другие файлы под названием “домен.crt”
+6. Скачиваем корневой и промежуточные сертификаты:
+ ```
+ wget https://gu-st.ru/content/Other/doc/russian_trusted_root_ca.cer
+ wget https://gu-st.ru/content/Other/doc/russian_trusted_sub_ca.cer
+ ```
+7. Преобразуем скачанный сертификат в формат PEM:
+ ```
+ openssl x509 -in neonxp.ru.crt -out neonxp.cer -outform PEM
+ ```
+8. Соединяем свой сертификат и минцифровские в один бандл:
+ ```
+ cat neonxp.cer russian_trusted_sub_ca_pem.cer russian_trusted_root_ca_pem.cer > chain.cer
+ ```
+9. Используем полученный бандл и сгенерированный в пункте 2 файл ключа в конфигурации вебсервера. У меня используется Caddy, поэтому мой конфиг выглядит так:
+ ```
+ neonxp.ru:443 {
+ tls /data/ssl/chain.cer /data/ssl/neonxp.ru.key
+ ...
+ }
+ ```
+
+В общем-то, всё. Как настанет время продлевать — я дополню заметку деталями
+именно продления. Если будут вопросы — пишите, попробуем решить.
diff --git a/content/posts/2024-06-01-вам-не-нужны-пуши.md b/content/posts/2024-06-01-вам-не-нужны-пуши.md
new file mode 100644
index 0000000..4d5960f
--- /dev/null
+++ b/content/posts/2024-06-01-вам-не-нужны-пуши.md
@@ -0,0 +1,110 @@
+---
+categories:
+- Без рубрики
+date: '2024-06-01T21:05:55Z'
+tags:
+- it
+- разное
+title: Вам не нужны пуши!
+---
+
+Я не шучу. Серьёзно.
+
+С неделю назад меня осенила крайне простая мысль, которая ранее, почему-то, мне
+не приходила.
+
+<!--more-->
+
+Но сначала, две вводных, или, скажем, тезиса, которые послужили для вывода этой
+мысли:
+
+## Тезис №1
+
+Меня действительно огорчает количество пушей которые постоянно сыплются мне на
+телефон. Это вызывает раздражение сразуна нескольких уровнях:
+
+1. Сам момент их прихода — я автоматически смотрю на телефон, что же пришло
+2. Если я игнорирую пуш — он потом висит в шторке вызывая раздражение
+3. Очень часто это сранная реклама от какого-нибудь озона или магнит маркета
+ (бывш. KazanExpress)
+
+Но бывают же и полезные пуши! Например, уведомления от Госуслуг или информация
+что заказ доставлен ну илисообщения в мессенджерах.
+
+То есть, как будто, ради вышеуказанных полезных пушей, я должен терпеть и тонну
+бесполезного говна!
+
+## Тезис №2
+
+Ну и вторая вводная, которая, какмне кажется подтолкнула меня — я всегда любил
+_простые_ и открытые технологии, какдревние, типа RSS, e-mail, irc, так и новые,
+но такие же простые и открытые, как, например, gemini (да, сейчас он наэтом
+сайте сломан, но я его починю на днях, честно!), федиверс и прочие подобные.
+Кстати, сейчас подумалось, что именноэти качества меня и так сильно влюбили в
+golang 🙂
+
+## Та самая простая мысль
+
+На стыке двух вышеуказанных тезисов у меня внезапно для себя самого и
+синтезировалась крайне простая мысль:
+
+> **<u>Действительно</u>** важные вещи всегда приходят на электропочту, а
+> сообщения в мессенджерах — это не срочно!
+
+Таким образом, запретив на телефоне вообще все пуши кроме электропочты я
+избавился от этого угнетающего информационногошума, оставив только полезный
+сигнал.
+
+-Хей, да на почте же один спам! — скажешь ты мне
+
+На самом деле, уже давно нет. Я лично использую почту mail.ru (в данном случае,
+это не очень важно и относится к любой)и на ней спама как такового уже давно нет
+(если думаешь, что это не так, перепроверь, возможно, твои
+представленияустарели). При этом, самое великое в этом то, что почта (на самом
+деле, не важно, какая именно — mail.ru, yandex или,прости господи, гмейл)
+предоставляет гибкие фильтры входящей почты. И потратив буквально пару десятков
+минут можносформировать правила, чтобы, например, от того же озона пропускались
+только письма со статусом заказа и больше ничего.
+
+Вот так, древняя технология обычных, старых-добрых, писем позволяет решить
+проблему современных назойливых уведомлений!
+
+При этом, почта не пушит проверять её постоянно! Самое главное её преимущество
+для меня — это её ассинхронность, вотличие от мессенджеров. Можно отключить от
+нее уведомления тоже, но завести себе правило, что раз в Н времени выделятьвремя
+на ее проверку. Самое главное — делать это в _комфортное для себя_ время.
+
+## Так же как и на мессенджеры, кстати!
+
+Выше я уже сказал, что мессенджеры — это не срочно. Ничего страшного не
+случится, если я отвечу через час-два-три иливообще вечером. Если будет что-то
+_действительно_ срочное — мне можно и позвонить. Но, к счастью, мне повезло, что
+мояжизнь достаточно спокойная и _действительно_ срочное почти не случается.
+Отрефлексируй, уважаемый читатель, насколько*действительно* срочные и важные
+вопросы, которые ты таковыми считаешь и которыми ежедневно дёргают тебя? И
+ответь себечестно, мир бы разрушился, если бы ты их отложил на комфортное для
+_себя_, а не других время?
+
+Такой эксперимент я ставлю на себе уже неделю. Я практически не захожу в
+мессенджеры, всё действительно важное мнеприходит на почту, лишней рекламы я не
+вижу, нет никакого информационного шума, который буквально стал
+бичомсовременности.
+
+## Вывод за неделю
+
+Моё внутреннее состояние ощущается как очень спокойное и, главное, комфортное. Я
+чувствую полный контроль над тем, чтои когда я потребляю и нет никакого
+информационного насилия, как его называет
+[Столяров](http://stolyarov.info/)(хоть мне этот персонаж и кажется чрезвычайно
+радикальным и оттого отталкивающим, но что-то в его словах таки есть).
+
+Считаю, что эксперимент оказался удачным, и я его продолжу!
+
+## Пишите письма!
+
+Напомню раз пришлось к слову, пожалуй, свою электропочту: <i@neonxp.ru> или
+<a.kiryukhin@mail.ru> обе почты абсолютноравноценны, писать можно на любую.
+Очевидно из поста, что молниеносный ответ я не гарантирую, но, сам факт ответа
+вобозримое время гарантирован!
+
+73!
diff --git a/content/posts/2024-06-02-книги-1.md b/content/posts/2024-06-02-книги-1.md
new file mode 100644
index 0000000..0173245
--- /dev/null
+++ b/content/posts/2024-06-02-книги-1.md
@@ -0,0 +1,21 @@
+---
+categories:
+- Без рубрики
+date: '2024-06-02T01:48:16Z'
+tags:
+- книги
+- фантастика
+title: Книжные рекомендации 1
+---
+
+Подумалось, почему бы не рекомендовать понравившиеся мне книги.
+
+В прошлый раз, ещё [в VK рекомендовал](https://vk.com/wall-174034751_45)
+Азимовский цикл “Основание”. А в этот раз рекомендую цикл фантастики Андре
+Нортона “Королева Солнца”.
+
+Если без спойлеров — цикл описывает приключения помощника супер-карго Дейла на
+космическом корабле вольных торговцев «Королева Солнца». Читается легко и
+увлекательно. Книги небольшие, проглатываются за пару часов.
+
+[Скачать](andre_norton-queen_of_sun.zip) \ No newline at end of file
diff --git a/content/posts/2024-07-13-joplin.md b/content/posts/2024-07-13-joplin.md
new file mode 100644
index 0000000..f23bcc7
--- /dev/null
+++ b/content/posts/2024-07-13-joplin.md
@@ -0,0 +1,43 @@
+---
+categories:
+- Без рубрики
+date: '2024-07-13T20:49:12Z'
+image: files/2024-07-13-joplin_joplin.webp
+tags:
+- it
+- joplin
+title: Заметочник Joplin
+---
+
+Просто хочу поделиться отличным приложением для заметок, вместо популярного
+Notion и менее популярного Obsidian.
+
+Название на для русского уха звучит по дурацки —
+[Joplin](https://joplinapp.org/). Но, не смотря на такое название,
+самоприложение очень даже серьёзное.
+
+В общем и целом, это достаточно продвинутый опенсорсный заметочник. В качестве
+формата текста он использует Markdown[^1].
+[^1]:https://skillbox.ru/media/code/yazyk-razmetki-markdown-shpargalka-po-sintaksisu-s-primerami/
+
+Так же, из приятностей — большое количество плагинов
+(<https://github.com/topics/joplin-plugin>) и возможность использовать свой
+сервер для синхронизации
+https://docs.vultr.com/how-to-host-a-joplin-server-with-docker-on-ubuntu .
+Для себя я, конечно же, поставил на свой сервак. Ну, а более бюджетно, если нет
+своего сервера — можно использовать любой WebDav сервер. В частности, [Облако
+Mail.Ru](https://help.mail.ru/cloud_web/app/webdav/) или Яндекс Диск (адрес
+<https://webdav.yandex.ru>, необходимо использовать [пароль
+приложения](https://yandex.ru/support/id/authorization/app-passwords.html)).
+
+Но почему же стоит поднять свой сервер? Ну хотя бы для того, чтобы иметь
+возможность спокойно публиковать заметки, например, вот так:
+<https://notes.neonxp.ru/shares/UKB6Rkgt2yA2q1yrwpvb8F>.
+
+Или возможность совместной работы, например, со своей парой над общим списком
+покупок.
+
+~~P.S. Если нужен аккаунт на моем сервере синхронизации Joplin — пишите на
+почту, самостоятельной регистрации на сервере синхронизации не предусмотренно.
+Вот только не забудьте при синхронизации включить в настройках шифрование
+заметок. Я не хочу потом получать подозрения в нарушение приватности.~~ \ No newline at end of file
diff --git a/content/posts/2024-07-21-bbs.md b/content/posts/2024-07-21-bbs.md
new file mode 100644
index 0000000..c304c84
--- /dev/null
+++ b/content/posts/2024-07-21-bbs.md
@@ -0,0 +1,33 @@
+---
+categories:
+- Без рубрики
+date: '2024-07-21T20:28:34Z'
+tags:
+- разное
+title: Преемственность от BBS до Телеграма
+---
+
+Чисто на правах воскресной шизы.
+
+Обнаружил для себя интересную тенденцию, в характерных своему времени
+инструментах для общения за последние 40+ лет:
+
+Если взять эволюционный ряд BBS (в т.ч. Фидо) → Форумы → Соцсети → Мессенджеры,
+то можно выделить в них несколько общихчерт:
+
+- Возможность общения 1—1
+- Возможность общения 1—М (оператор BBS, администратор форума может сделать
+ какую-то тему в read-only и сам туда писать,получая что-то типа каналов в
+ телеге или блога)
+- Возможность общения М—М (обычный режим форума или многопользовательский чат в
+ мессенджерах)
+- Возможность обмена файлами (в т.ч. картинками, не зависимо от того, сразу они
+ отображаются у собеседника или нет)
+- Возможность проводить голосования (внезапно, да?)
+
+Что из этого следует? Да ничего, просто забавно. Интересно, что будет в
+постмессенджеровую эпоху? По идее, какой быинструмент ни был — эти же черты
+будут присущи и ему.
+
+P.S. Да, я тут не упомянул про мейллисты и условные IRC, но просто не знал куда
+и после чего их приткнуть. Но по факту,черты все те же самые. \ No newline at end of file
diff --git a/content/posts/2024-09-26-hugo-wordpress.md b/content/posts/2024-09-26-hugo-wordpress.md
new file mode 100644
index 0000000..aa862ef
--- /dev/null
+++ b/content/posts/2024-09-26-hugo-wordpress.md
@@ -0,0 +1,16 @@
+---
+categories:
+- Без рубрики
+date: '2024-09-26T19:05:00Z'
+tags:
+- блог
+title: Hugo → WordPress
+---
+
+Поменял в блоге движок с модного Hugo на немодный бумерский WordPress. Почему?
+Да просто он удобнее.
+
+Серьёзно, неужели этот гиковский пердолинг с сборкой блога через Git CI удобнее
+чем просто написать пост в браузере? Ну если не врать себе, то конечно же нет.
+
+Так что да, с возрастом начинаешь ценить просто удобные, а не новомодные вещи. \ No newline at end of file
diff --git a/content/posts/2024-10-06-цитатник-рунета.md b/content/posts/2024-10-06-цитатник-рунета.md
new file mode 100644
index 0000000..cc9ba60
--- /dev/null
+++ b/content/posts/2024-10-06-цитатник-рунета.md
@@ -0,0 +1,64 @@
+---
+categories:
+- Мои проекты
+date: '2024-10-06T12:00:11Z'
+image: files/2024-10-06-цитатник-рунета_bash_org.webp
+location: Казань
+tags:
+- go
+- it
+- моё
+title: Цитатник Рунета
+---
+
+В середине-конце нулевых был очень популярный сайт баш.орг.ру. Думаю, те, «кому
+за» помнят ещё такой.
+
+Сайт просто был сборником цитат из разных чатов, irc каналов или личных
+переписок. Изначально, был исключительно анимешно-айтишной направленности и тем
+самым для нас, студентов и гиков был крайне популярным местом. В своё время, он
+подарил мне много часов приятного времяпрепровождения и ламповых вечеров.
+
+Затем, когда БОР (как часто его сокращали) выиграл премию Рунета, на него хлынул
+поток, как сейчас бы сказали, «нормисов». Которые, уже в свою очередь, заполнили
+БОР всяким про отношения, офисно-планктонные темы, фейковыми цитатами, ответами
+на цитаты, ответами на ответы на цитаты и прочим подобным, далёким от
+изначального айтишного флёра, шлаком.
+
+В общем, как всегда, в андеграунд пришли нормисы и всё испортили. И да, баш
+скатился уже, по сути, к десятым годам.
+
+Примерно тогда же он для меня и закончился, ибо стал уже совсем не «торт». Потом
+он как-то жил больше декады за границами моего внимания. Успев при этом поменять
+адрес с зоны .ru на зону .im зачем-то. Ну а с началом СВО его админы
+окончательно сошли с ума и закрыли БОР который к тому времени и так едва ли был
+жив. На этом, его история окончательно закончилась.
+
+Однако, не смотря на это БОР был интересным и знаковым феноменом, который
+неотрывно вписан как в историю рунета так и в мою личную историю юности.
+
+Посему, я решил, так сказать, или возродить его, ну или, как минимум, сделать
+ему мемориал.
+
+Сказано — сделано. Купил домен, который отсылается к самому старому домену
+оригинала — [sh.org.ru](https://sh.org.ru) (sh является командной оболочкой,
+предком командной оболочки bash). За несколько часов написал скраппер по
+зеркалам и архивам бора, спарсил более 80К цитат. Затем, написал на golang
+простенький движок и всего за день запустил свой цитатник в свободное плавание!
+
+Из функций пока только вывод цитат по страницам, а так же вывод случайных 20
+цитат + кнопка для выдачи других 20 случайных. Лично мне гораздо больше нравятся
+как раз случайные подборки. Их можно обновлять почти бесконечно!
+
+Да, он пока не умеет принимать новые цитаты (да и кто их будет слать то, лол?),
+да и нет других функций, типа голосований (классическими `[+]`, `[-]`,
+`[:|||:]`). Буду ли я это доделывать и как-то развивать? Не знаю. Возможно,
+время цитатника безвозвратно ушло. Но может быть и внезапный комбек. Кто знает
+🤷‍♂️. В ближайшие дни я допилю и голосвалку и добавление цитат, но вряд ли буду
+в это инвестировать много времени. Есть ещё и мысль публиковать цитаты через ТГ
+бота простой пересылкой ему сообщений, а он уже их сам анонимизирует заменяя
+данные пользователей на обезличенные XXX и YYY и оформляет цитату как надо. Как
+вам такая идея?
+
+Вообще, я бы хотел это как-то, наверное, обсудить, относительно того как это
+развивать и стоит ли? \ No newline at end of file
diff --git a/content/posts/2024-10-17-книги-2.md b/content/posts/2024-10-17-книги-2.md
new file mode 100644
index 0000000..96cafc7
--- /dev/null
+++ b/content/posts/2024-10-17-книги-2.md
@@ -0,0 +1,39 @@
+---
+categories:
+- Без рубрики
+date: '2024-10-17T19:26:00Z'
+image: files/2024-10-17-книги-2_Rama16wiki.webp
+location: Казань
+tags:
+- книги
+title: Книжные рекомендации №2
+---
+
+Продолжу, пожалуй.
+
+Сегодня хочу порекомендовать всего две книги:
+
+## Свидание с Рамой
+
+Артур Кларк, 1973
+
+Фантастическая повесть о встрече человечества с необитаемым(?) инопланетным
+кораблём, который прилетел в нашу солнечную систему. На изображении выше — вид
+этого корабля изнутри.
+
+- На сайте lib.ru:
+ [www.lib.ru/KLARK/rama1.txt](http://www.lib.ru/KLARK/rama1.txt)
+- В виде аудиокниги: <https://akniga.org/klark-artur-svidanie-s-ramoy>
+- Если надо — могу выложить по запросу в формате fb2
+
+## Глубина в небе
+
+Вернор Виндж, 1999
+
+Об экспедиции двух разных человеческих колоний к странной звезде, имеющей
+свойство выключаться на 200 лет. Причиной отправки стали принятые с окрестной
+планеты радио сигналы, свидетельствующие о наличии разумной жизни на ней.
+
+Книга является частью цикла, и я прикладываю цикл целиком:
+
+[Цикл «КенгХо» скачать](КенгХо.zip) \ No newline at end of file
diff --git a/content/posts/2024-11-15-hugo.md b/content/posts/2024-11-15-hugo.md
new file mode 100644
index 0000000..7a677a4
--- /dev/null
+++ b/content/posts/2024-11-15-hugo.md
@@ -0,0 +1,19 @@
+---
+categories:
+- Без рубрики
+date: '2024-11-15T01:11:49+03:00'
+location: Казань
+tags:
+- разное
+title: Hugo
+---
+
+Так, ну я вернулся на hugo :D
+
+Основная причина — я нашел решение основной моей проблемы с Hugo, а именно,
+удобной публикации.
+
+А как именно решил — тема отдельного поста на потом.
+
+Ну и тему наконец-то сделал сам с нуля. Как говорится, хочешь сделать хорошо —
+сделай это сам. \ No newline at end of file
diff --git a/content/posts/2024-11-17-obsidian.md b/content/posts/2024-11-17-obsidian.md
new file mode 100644
index 0000000..9ecf5f4
--- /dev/null
+++ b/content/posts/2024-11-17-obsidian.md
@@ -0,0 +1,240 @@
+---
+categories:
+- Без рубрики
+date: '2024-11-17T22:30:37+03:00'
+description: ''
+image: files/2024-11-17-obsidian_img/logo.webp
+location: Казань
+tags:
+- it
+- joplin
+- obsidian
+title: Obsidian
+---
+
+Некоторое время назад я [писал](/posts/2024-07-13-joplin/) про заметочник
+Joplin.
+
+С тех пор мои вкусы несколько поменялись и я открыл для себя его величество
+[Obsidian](https://obsidian.md/).
+
+В целом он такой же заметочник, с ± тем же функционалом, но имеет для меня одну
+особенность, которая буквально переворачивает всё. Это мощнейшая система
+плагинов. Серьёзно, я нашел плагины которые покрывают для меня всё, кроме одного
+(но об этом позже).
+
+## Что такое Obisidian?
+
+Obsidian представляет собой приложение для ведения персональных баз данных,
+основанное на принципах локальных файлов Markdown. Это значит, что ваши данные
+хранятся в виде обычных текстовых файлов, что обеспечивает максимальную гибкость
+и независимость от облачных сервисов.
+
+Приложение работает на операционных системах: Windows, macOS, Linux, iOS,
+Android.
+
+<!--more-->
+
+## Основные функции и преимущества
+
+1. **Граф связей** — да, он есть уже много где, но нельзя его не упомянуть.
+2. **Markdown** — очень приятно, что все заметки хранятся в Markdown, что
+ обеспечивает максимальную интероперабельность и переносимость
+3. **Плагины** — плагины пишутся на JS/TS и их много. Даже не так, их **МНОГО**.
+ Что приятно, они скачиваются и лежат в той же директории что и основное
+ хранилище, а это важно для следующего пункта
+4. **Синхронизация** — она есть. Но вроде как платная. Но мне это и не
+ интересно, я использую Syncthing. Просто шарю через него директорию
+ хранилища по схеме оба ноутбука <-> NAS <-> Android. При этом синкаются все
+ плагины и настройки.
+5. **Скорость** — не смотря на то, что он написан на проклятом электроне,
+ работает достаточно шустро, претензий нет.
+6. **Доска для рисования** — мелочь, конечно, но удобно, когда надо на скорую
+ руку накидать небольшую схемку. В конце этого поста как раз есть пример
+ такой схемки.
+
+## Минусы
+
+1. **Проприетарность** — Obsidian хоть и в целом бесплатный, но он не свободный
+ и даже не opensource. Да, это серьёзный минус, но он компенсируется тем, что
+ хотябы вся база данных не в проприетарном формате. И в случае чего можно
+ будет с наименьшими проблемами свалить куда-нибудь.
+2. **Electron** — ну это скорей мой личный пунктик. Но при этом приходиться
+ смиряться с электроном что на Obsidian что на VSCode (VSCodium, конечно же),
+ потому что лучше-то и нет.
+
+Это только то, что сейчас пришло в голову.
+
+## А теперь самое вкусное
+
+Не помню, я упоминал что у него много плагинов? :)
+
+Так вот, поехали, мои самые любимые:
+
+### Dataview
+
+https://blacksmithgu.github.io/obsidian-dataview/
+
+Ну это просто must-have плагин, который позволяет обращаться с вашими заметками
+именно как с базой данных, не меньше.
+
+Например, можно создать новый документ, написать в него
+
+```
+ ```dataview
+ TASK
+ WHERE status = " "
+ ```
+```
+
+и волшебным образом вместо этого блока появятся все невыполненные задачи, а вот
+так
+
+```
+ ```dataview
+ TASK
+ WHERE status = "x"
+ ```
+```
+
+мы получим все выполненные.
+
+Язык запросов очень мощный[^1], в нём сто́ит разобраться.
+[^1]:https://blacksmithgu.github.io/obsidian-dataview/queries/structure/
+
+Ещё есть возможность делать однострочные запросы, например, в домашней заметке
+(которая у меня открывается по умолчанию) у меня есть ссылка на именно
+сегодняшнюю заметку ежедневного журнала. Сделано вот так:
+
+```
+`=link(dateformat(date(today), "yyyy.MM.dd"))`
+```
+
+### Templater
+
+https://silentvoid13.github.io/Templater/
+
+Этот плагин позволяет мне задать некоторым директориям умолчальный шаблон.
+Например, вот такой у меня шаблон для ежедневных журналов:
+
+```
+<%*
+try {
+ // Получаем имя текущей ежедневной заметки
+ const noteName = tp.file.title;
+
+ // Разбиваем полученное имя на компоненты даты
+ const [year, month, day] = noteName.split('.').map(Number);
+
+ // Создаём объект Date на основе поученных компонентов
+ const currentNoteDate = new Date(year, month - 1, day);
+
+ // Вычисляем предыдущий и следующий день
+ let previousDayDate = new Date(currentNoteDate.setDate(currentNoteDate.getDate() - 1));
+ let nextDayDate = new Date(currentNoteDate.setDate(currentNoteDate.getDate() + 2));
+
+ // Форматируем дату обратно в "DD-MM-YYYY"
+ const formatDate = (date) => {
+ const dd = String(date.getDate()).padStart(2, '0');
+ const mm = String(date.getMonth() + 1).padStart(2, '0');
+ const yyyy = date.getFullYear();
+ return `${yyyy}.${mm}.${dd}`;
+ };
+
+ const previousDay = formatDate(previousDayDate);
+ const nextDay = formatDate(nextDayDate);
+
+ // Формируем ссылки
+ const baseFolder = tp.file.folder(true);
+ const previousNotePath = `${baseFolder}/${previousDay}.md`;
+ const nextNotePath = `${baseFolder}/${nextDay}.md`;
+
+ // Выводим даты в виде ссылок
+ tR += `← [[${previousNotePath}|${previousDay}]] | [[${nextNotePath}|${nextDay}]] →`;
+} catch (error) {
+ console.error("Templater Error:", error);
+}
+%>
+
+## Задачи
+___
+<%
+`- [ ]`
+%>
+
+## Заметки
+___
+
+```
+
+и переходя к сегодняшней заметке я сразу получаю такую заготовку:
+
+![Заметка из шаблона](/posts/files/2024-11-17-obsidian_img/templater.webp "Заметка из шаблона")
+
+### Остальные плагины
+
+Остальные тоже крутые, но я их приведу просто списком:
+
+- [tasks](https://publish.obsidian.md/tasks/Introduction) — помогает более
+ богато управлять задачами. В частности, у меня проставляет дату завершения
+ задачи, и проставляет даты дедлайна и прочее.
+- [reminder](https://uphy.github.io/obsidian-reminder/) — трекает и напоминает
+ про задачи
+- [calendar](https://github.com/liamcain/obsidian-calendar-plugin) — просто
+ миникалендарь в боковой панели
+- [homepage](https://github.com/mirnovov/obsidian-homepage) — позволяет задать
+ произвольную заметку "домашней"
+- [icon-folder](https://github.com/timolins/obsidian-icon-folder) — позволяет
+ задавать директориям и заметкам произвольные иконки. Пример есть как раз на
+ скриншоте выше.
+- [pomodoro-timer](https://github.com/eatgrass/obsidian-pomodoro-timer) — думаю,
+ из названия и так понятно
+- [kanban](https://publish.obsidian.md/kanban/) — шикарнейший канбан плагин
+
+## А что же мне не хватает?
+
+Я упомянул выше что мне кое чего не хватает. А именно, постить заметку в мой
+блог по протоколу [Micropub](https://indieweb.org/Micropub).
+
+Только из-за Obsidian и того, что он использует Markdown я опять [вернулся на
+Hugo](/posts/2024-11-15-hugo/), который так же рендерится из Markdown.
+
+«Но Hugo это же генератор статичных сайтов, куда ты ему будешь отправлять
+заметку для публикации?» — можешь спросить меня ты. А я отвечу что у меня вот
+такой план:
+
+```mermaid
+graph TB
+b1["Заметка в Obsidian"]
+b2["Плагин obsidian-micropub"]
+b3["micropub сервер на моем сервере"]
+b4["вызов hugo"]
+b5["Загрузка или копирование результата на веб сервер"]
+b1 --> |Publish в контекстном меню| b2
+b2 --> |POST neonxp.ru/micropub| b3
+b3 --> |Запись в директорию content блога| b4
+b4 --> |hugo рендерит markdown -> html| b5
+style b1 fill:#28252e, stroke:#754fcc
+style b2 fill:#2e2121, stroke:#c81319
+style b3 fill:#2e2121, stroke:#c81319
+style b4 fill:#222c2c, stroke:#20acaa
+style b5 fill:#222c2c, stroke:#20acaa
+```
+
+То что выделено красным — ещё не существует в природе.
+
+micropub сервер для hugo я уже начал писать. Да, есть nanopub сервер, но у него
+есть два серьёзных недостатка, это PHP и то что его сделал не я.
+
+micropub плагин для obsidian я вижу сделать на основе существующего плагина
+rest-publish. Ну или как пойдёт.
+
+В общем, меня ждёт ещё очень много весёлого дрочева с этим всем.
+
+## Закругляюсь
+
+Пожалуй, пока на этом всё. Поделился как радостью использования Obsidian, так и
+планами на пет-проекты, что ещё надо-то?
+
+Если что, пишите комментарии. Лучше всего здесь, но можно и во всяких
+телеграмах-вкшках.
diff --git a/content/posts/2024-11-27-hyperlocality.md b/content/posts/2024-11-27-hyperlocality.md
new file mode 100644
index 0000000..eb8ff84
--- /dev/null
+++ b/content/posts/2024-11-27-hyperlocality.md
@@ -0,0 +1,182 @@
+---
+categories:
+- гиперлокальность
+date: '2024-11-27T17:50:18+03:00'
+description: ''
+location: Казань
+tags:
+- разное
+- IT
+- размышления
+- гиперлокальность
+title: Гиперлокальность
+---
+
+Это очередной пост моих пространных рассуждений про тенденции и будущее
+интернета, которых в последнее время становится как-то многовато. Вероятно, в
+последствии, это станет даже серией постов.
+
+Этот же я воспринимаю, как вводный в лор гиперлокальности.
+
+Сначала, пожалуй, расскажу про посылки, а потом уже о том, куда они ведут, и
+какие из этого можно сделать выводы.
+<!--more-->
+## Посылка
+
+Думаю, все мы заметили как много вокруг стало ИИ инструментов. Сейчас ИИ на
+хайпе и его засовывают буквально куда можно и куда нельзя. Само по себе меня это
+не беспокоит. Я отношусь к ИИ как к просто очередному инструменту, который можно
+и нужно использовать там, где он применим. С этим нет проблем. Пройдёт какое-то
+время и ИИ инструменты займут ниши, где они наиболее уместны и где от них
+наибольшая польза. Однако тут есть и негативный нюанс. Этот инструмент будет
+способствовать в том числе и тому, что интернет станет (если ещё не стал!) по
+сути своей «мёртвым». Не мёртвым буквально, а «мёртвым» в том же смысле, в
+котором в «Руководстве путешествующего автостопом по галактики» Д. Адамса была
+вселенная обозначена необитаемой.
+
+> Вселенная — кое-какая информация, облегчающая существование в ней.
+>
+> <...>
+>
+> 4. Население: Отсутствует. Известно, что существует бесконечное множество
+> планет. Это объясняется той простой причиной, что пространство, в котором они
+> могут существовать, также бесконечно. Однако не всякая из этих планет
+> обитаема. Отсюда следует, что число обитаемых планет конечно. Частное от
+> деления любого конечного числа на бесконечность стремится к нулю и не дает
+> остатка, следовательно, можно заключить, что средняя численность населения
+> планет Вселенной равна нулю. Отсюда следует, что численность населения во всей
+> Вселенной также равна нулю, и потому все люди, которые порой попадаются на
+> вашем пути, являются продуктом вашего воспаленного воображения.
+>
+> Д. Адамс — Ресторан «У края Вселенной», 19 глава
+
+## Следствие
+
+Количество сгенерированного ИИ контента, ИИ ботов пишущих комментарии и иным
+способом имитирующих людей будет расти нелинейно. Таким образом будет
+«размываться» весьма конечное количиство «живых» пользователей «неживыми» до
+того, что все эти миллиарды «живых» пользователей будут лишь статистической
+погрешностью относительно «неживых» ИИ ботов.
+
+## Как это повлияет на наше восприятие реальности?
+
+Представьте себе мир, где большинство сообщений, комментариев и публикаций
+создаются ИИ. Мы будем жить в мире, где трудно отличить реальность от иллюзии.
+Где каждый день нам придётся задаваться вопросом: кто написал этот комментарий –
+реальный человек или искусственный интеллект? Это приведёт к тому, что доверие к
+информации в интернете начнёт стремительно падать. Люди станут всё больше
+сомневаться в подлинности того, что видят и читают. В итоге, интернет
+превратится в огромное море данных, где настоящие голоса людей тонут в океане
+фальшивок и симуляций.
+
+![Киберпанк который мы
+заслужили](/posts/files/2024-11-27-hyperlocality_img/braindance.webp "Киберпанк
+который мы заслужили" )
+
+## Гиперлокальность
+
+Уже сейчас вполне себе просматиривается контур того, что я, за неимением лучшего
+термина, называю «Гиперлокальностью». Термин мне нравится тем что он, с одной
+стороны, хорошо описывает то, куда, по моему мнению, мы придём, а с другой
+стороны, названием отсылает к «гипертексту».
+
+### Что я под этим подразумеваю?
+
+Помните старые времена, когда интернет только-только появлялся и из каждого
+утюга звучало как одно из его преимуществ, то, что «вы сможете находить себе
+собеседников и друзьей в любой точке мира, не выходя из дома». Звучало
+многообещающе, и в каком-то смысле, оно так и было.
+
+![Наивное представление из 90х о том, что интернет будет нас
+соединять](/posts/files/2024-11-27-hyperlocality_img/90e.webp "Наивное
+представление из 90х о том, что интернет будет нас соединять")
+
+Но что происходит сейчас? Интернет, вместо того чтобы соединять людей по всему
+миру, начинает дробиться на маленькие замкнутые круги. Почему так происходит?
+Ответ кроется в недоверии. Когда невозможно понять, кто перед тобой – настоящий
+человек или ИИ-бот, люди начинают замыкаться в узких кругах тех, кому они
+доверяют.
+
+> Интернет, он не сближает. Это скопление одиночества. Мы вроде вместе, но
+> каждый один. Иллюзия общения, иллюзия дружбы, иллюзия жизни…
+>
+> Януш Леон Вишневский — Одиночество в Сети
+
+Эти круги становятся всё меньше и меньше, пока не превращаются в замкнутые
+сообщества, где общение ограничено только теми, кого знаешь лично. Таким
+образом, получается некая WebOfTrust, но только по валидации «человечности». Это
+напоминает модель «доверительных сетей», которая существовала задолго до
+появления интернета, но теперь она приобретает новый смысл в цифровую эпоху.
+
+А личные знакомства они, как правило, достаточно локальные. А следовательно, в
+ближайшее время мы увидим расцвет изолированных «анклавов» из _лично_ знакомых
+между собой людей, который и будут существовать своими маленькими,
+**гиперлокальными** сообществами. Размер при этом может быть почти любой, как
+группка из трёх друзей, так и небольшой клуб из пары десятков _лично знакомых_
+единомышленников.
+
+Причём, примеры гиперлокальных сообществ уже сейчас есть и в большом количестве.
+Например, у меня с друзьями уже почти 10 лет есть свой маленький чатик на шесть
+голов. И, в принципе, этого круга общения мне вполне хватает. И в своём кругу
+мы, конечно же, уверенны в «человечности» каждого из нас, ибо знакомы и ИРЛ.
+
+Причём, «достаточность» этого кружка для меня такая, что если у меня, вдруг,
+магическим образом, останется только этот чатик, мой NAS в который загруженно
+примерно 50К книг и несколько любимых сериалов, и, конечно, VPN до работы, чтобы
+я мог зарабатывать на жизнь — то, это и будет вся моя гиперлокальная сеть. И как
+будто, не сильно то я и потеряю если останется только это, ну или как минимум,
+уж точно выживу. Если что, это именно магически и гипотетически, но тем не
+менее.
+
+Так же, подобные кружки, я видел и, например, у своих старших родственников.
+Они, в основном, устраивают гиперлокальные «кружки» в том же вотсаппе. Там они
+делятся рекомендациями фильмов, рецептами, шутками, новостями и прочим подобным.
+
+### А к чему я это всё?
+
+Да к тому что на текущем этапе развития интернета, мы всё больше уходим от
+**глобальной** сети к **гиперлокальной**. И, наверное, мне это даже вполне
+нравится. Это как-то... уютно чтоли.
+
+![Примерно так я визуализирую гиперлокальное
+сообщество](/posts/files/2024-11-27-hyperlocality_img/camp.webp "Примерно так я
+визуализирую гиперлокальное сообщество" )
+
+## Перспективы
+
+Дисклеймер. Дальше идут мои размышления, которые основываются в основном на
+интуитивных, а не объективных предположениях
+
+С развитием этого тренда будут всё больше и больше отмирать крупные социальные
+сети типа ВК или РКНбука. История сделает виток и восскресит т.н. локалки,
+которые были популярны в 90е-00е. Конечно же, уже в другом облике. Никто не
+будет лазать по чердакам чтобы протянуть витуху между соседями, но именно суть
+останется. А суть в том, что будет бо́льшая концентрация на небольшом числе
+условно локальных ресурсов, где человек будет только со своими друзьями, а
+«большой» интернет отходит на второй план.
+
+Так же могут получить развитие indieweb технологии, а так же self-hosted решения
+для общения, например, Matrix. Эти инструменты потребуются как ответ на
+заполненные ботами и спамом соцсети и мессенджеры. Конечно же, всё что нужно не
+затащить в свою уютненькую локалочку, но вылазка за недостающей инфой в интернет
+будет ощущаться, как выход из своей зоны комфорта в дикую и опасную пустошь.
+
+![Вылазка в «большой»
+интернет](/posts/files/2024-11-27-hyperlocality_img/in-internet.webp "Вылазка в
+«большой» интернет")
+
+## Окончание?
+
+Я отдаю себе отчёт что то, что я написал выше — весьма сумбурно. Но это
+следствие того, что я ещё не до конца исследовал эту тему, и многие мысли на эту
+тему в моей голове пока ещё не сформированы в слова, а остаются на интуитивном
+уровне.
+
+А написал я это, скорее как повод начать дискуссию на эту тему. Мне интересно,
+что вы думаете по этой теме. Возможно, мнение со стороны меня наведёт на еще
+какие мысли.
+
+В дальнейшем у меня уже есть некоторые мысли на развитие темы, но уже в каких то
+отдельных аспектах.
+
+Остаёмся на связи, 73!
diff --git a/content/posts/2024-11-29-hobbies.md b/content/posts/2024-11-29-hobbies.md
new file mode 100644
index 0000000..50fc928
--- /dev/null
+++ b/content/posts/2024-11-29-hobbies.md
@@ -0,0 +1,44 @@
+---
+categories:
+- Мысли вслух
+date: '2024-11-29T18:00:36+03:00'
+description: ''
+image: files/2024-11-29-hobbies_dozor.webp
+location: Казань
+tags:
+- размышления
+title: Откуда берутся увлечения?
+---
+
+На днях задался вопросом вынесенным в заголовок. Причём не столько над
+эволюцией, сколько о том, откуда они взялись.
+
+Раньше я часто играл в ночные полевые игры, такие как «Дозоры» и «Энкаунтеры».
+Они были мне очень интересны и играли важную роль в моей жизни. Даже, я бы
+сказал, во взрослении, так как пришлись на возраст 19–25 лет.
+
+<!--more-->
+
+Сейчас давно уже не играю, но многие мои нынешние увлечения берут начало именно
+оттуда. Например, самый очевидный пример — интерес к картографии и ГИСам
+(геоинформационным системам), поскольку для «Дозоров» они были неотъемлемой
+частью. Также сюда относится и то, что я хорошо ориентируюсь в своем городе и
+немного интересуюсь его историей.
+
+Кроме того, можно проследить интерес к радиоэлектронике: пару раз, когда с
+командой организовывали игры, я придумывал задания, основанные на простых
+электронных устройствах, собранных на микроконтроллерах.
+
+Туда же и любовь к исключительно ноутбукам, да и вообще переносной, что важно,
+технике. Теплое отношение к простым, надёжным, нетребовательным технологиям. Это
+всё оттуда же!
+
+Любовь к программированию у меня возникла чуть раньше, поэтому её связать с
+этими играми я не могу. Здесь, скорее случилось наоборот. И я не менее трех раз
+даже порывался писать собственный «движок» для НПИ. Но, пока ни разу не успешно.
+Вероятно, меня здесь привлекает процесс, а не результат. Так что, последний мой
+заход хоть и является вполне себе функционально законченным, но для его развития
+времени я не выкраиваю, к сожалению.
+
+Что-то еще было, но я не успел записать, и теперь забыл. Вывод: не стоит давать
+остывать размышлениям дольше суток, а писать сразу 🙂 \ No newline at end of file
diff --git a/content/posts/2024-12-12-guessr.md b/content/posts/2024-12-12-guessr.md
new file mode 100644
index 0000000..235901c
--- /dev/null
+++ b/content/posts/2024-12-12-guessr.md
@@ -0,0 +1,130 @@
+---
+categories:
+- Мои проекты
+date: '2024-12-12T22:27:49+03:00'
+description: ''
+image: files/2024-12-12-guessr_logo.webp
+location: Казань
+tags:
+- IT
+- Проект выходного дня
+title: Guessr
+---
+
+На недавних выходных я запилил очередной «проект выходного дня». На этот раз —
+аналог известного сервиса GeoGuessr, но в отличие от него, все точки
+сконцентрированы в моей родной Казани. Ну и я не использую панорамы, а
+фотографии мест.
+
+Я обещал выложить исходники, и в общем, вот они:
+https://git.neonxp.ru/guessr.git/
+
+## Немного про разработку
+
+Первым встал вопрос, откуда брать данные, а именно фотографии и координаты
+точек. Пару лет назад нашу страну покинул такой проект, как Ingress,
+представлявший собой гео игру в дополненной реальности. В свою очередь, я
+посчитал, что раз проект решил отказаться от нас, как игроков, я посчитал
+морально оправданным ~~спиз~~экспропреировать кусочек их данных, а именно
+спарсил с их карты intel.ingress.com т.н. «порталы», которые, по сути и есть эти
+самые геоточки с фотографиями.
+
+Дамп я загнал в Postgresql с подключенным расширением
+[Postgis](https://postgis.net/).
+
+Ну а далее написал достаточно простой API на Golang, который реализует следующие
+методы:
+
+- Создание новой игровой сессии, в ответ ставится кука внутри которой
+ зашифровано текущее состояние — ник, количество очков, ID текущего
+ угадываемого объекта (в начале пустое).
+ ```http
+ POST /api/state
+ Content-Type: application/json
+
+ {
+ "username": "NeonXP"
+ }
+ ```
+
+- Получение состояния. Просто возвращает вышеуказанные параметры
+ ```http
+ GET /api/state
+ ```
+
+- Выдача нового объекта для угадывания. При этом возвращается ссылка на фото и
+ обновляется состояние, тем что в него вписывается ID объекта
+ ```http
+ POST /api/next
+ ```
+
+- Угадывание. Собственно, на вход передаются координаты куда на карте указал
+ игрок. А в ответ возвращается:
+ - Название объекта
+ - Расстояние от переданной точки до реального размещения объекта
+ - Geojson строка в которой зашифрована линия соединяющая точку и объект (нужна
+ для отрисовки красной линии на карте)
+
+ При этом высчитываются очки которые получает игрок за попытку по формуле
+ max(1000-d, 0), где d - расстояние между выбранной точкой и объектом в метрах.
+ То есть, если разница меньше 1000м, то чем ближе - тем больше очков (максимум
+ 1000 очков за 1 очень точное угадывание).
+ ```http
+ POST /api/guess
+ Content-Type: application/json
+
+ {
+ "lat": 55.123,
+ "lon": 49.123
+ }
+ ```
+
+Вот в общем-то и всё API!
+
+Из интересностей, при выборе очередной точки у неё в БД увеличивается счетчик, а
+сам select выбирает случайную точку только среди тех точек, где этот счетчик
+минимальный. То есть, пока не будут выданы игрокам все точки, уже выбранные
+заново не будут выданы. Вот это место в коде:
+https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n26 (стр. 26-32)
+
+```go
+err = btx.NewSelect().
+ ColumnExpr(`p.guid, p.img`).
+ Model(r).
+ Where(`p.count = (SELECT MIN(pl.count) FROM places pl WHERE pl.deleted_at IS NULL)`).
+ OrderExpr(`RANDOM()`).
+ Limit(1).
+ Scan(ctx, r)
+```
+
+Ещё я бы отметил то, что я решил по максимуму логику вынести в БД, и, например,
+при угадывании расстояние до точки, а также вышеупомянутый geojson формируются
+так же на стороне БД:
+https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n50 (стр. 50-59)
+
+```go
+err := p.db.NewSelect().
+ Model(&model.Place{GUID: guid}).
+ WherePK("guid").
+ ColumnExpr(`p.name, p.guid, p.img,
+ ST_Distance(ST_MakePoint(?, ?)::geography, p.position::geography)::int AS distance,
+ ST_AsGeoJSON(ST_MakeLine(
+ ST_SetSRID(ST_MakePoint(?, ?), 4326),
+ ST_SetSRID(p.position, 4326)
+ )) AS geojson`, lon, lat, lon, lat).
+ Scan(ctx, r)
+```
+
+## Дальнейшие планы
+
+В комментах к анонсу ребята накидали достаточно много хороших идей, синтезировав
+которые, и добавив свои хотелки я составил примерно такой чеклист:
+
+- [ ] Авторизация и общая доска лидерства
+- [ ] После угадывания спрашивать у игрока «сложность», чтобы потом можно было,
+ например, настраивать чтобы попадались только простые объекты. И, например,
+ разное количество очков за простые и сложные объекты
+- [ ] Подумать как вынести игру в оффлайн, по типу того же ингресса. Это сложно
+ и предстоит хорошо это обдумать
+
+Как-то так :) А впереди новые выходные и новые «проекты выходного дня»! \ No newline at end of file
diff --git a/content/posts/2024-12-15-conditional-operator-go.md b/content/posts/2024-12-15-conditional-operator-go.md
new file mode 100644
index 0000000..480ba42
--- /dev/null
+++ b/content/posts/2024-12-15-conditional-operator-go.md
@@ -0,0 +1,35 @@
+---
+categories:
+- Без рубрики
+date: '2024-12-15T23:47:08+03:00'
+description: ''
+image: files/2024-12-15-conditional-operator-go_ternary.webp
+location: Казань
+tags:
+- IT
+- Go
+title: Тернарник в Go
+---
+
+Хотите немного ~~наркомании~~ сахара для Go?
+
+Их есть у меня:
+
+Тернарный оператор для Go на генериках
+
+```go
+func If[T any](condition bool, thn T, els T) T {
+ if condition {
+ return thn
+ }
+ return els
+}
+```
+
+[Плейграунд чтобы потыкать](https://go.dev/play/p/sBDnPGHce8I)
+
+Будет настроение — добавлю в свою либку https://neonxp.ru/go/extra , а пока,
+как-то так держите.
+
+**Не стоит** использовать в реальном коде. Я лично не вижу никакого оправдания
+для использования, кроме как покекать.
diff --git a/content/posts/2024-12-15-posse.md b/content/posts/2024-12-15-posse.md
new file mode 100644
index 0000000..023e1e2
--- /dev/null
+++ b/content/posts/2024-12-15-posse.md
@@ -0,0 +1,81 @@
+---
+categories:
+- Блог
+date: '2024-12-15T22:10:46+03:00'
+description: ''
+image: files/2024-12-15-posse_posse.webp
+location: Казань
+tags:
+- блог
+- разное
+title: POSSE
+---
+
+Решил я перейти к использованию практики POSSE. Что это такое? Аббревиатура
+расшифровывается примерно следующими способами:
+
+**P** - Publish или Post, **OS** - Own Site, **SE** - Syndicate Elsewhere (мне
+больше нравится, Share Everywhere)
+
+Это практика, когда изначально любой материал публикуется на полностью
+подконтрольном собственном сайте, а только затем переразмещаяется на всякие
+социальные сети, типа ВК, Телеги и прочих Мастодонов.
+
+<!--more-->
+
+## Почему это важно?
+
+* Во-первых, **платформы ненадежны**. Любая платформа в любой момент может
+ сделать что угодно с вашим контентом, или закрыться.
+* Во-вторых, **право собственности**. Не секрет, что у платформ весьма вольное
+ представление об авторском праве на материалы размещаемые пользователями. С
+ одной стороны, у них неограниченное право распоряжения контентом для любых
+ целей, а с другой никакой ответственности за содержание контента. Не слишком
+ ли кучеряво? А следуя POSSE, я и все кто следуют POSSE — сохраняют
+ первоисточник под своим контролем, отдавая платформам лишь небольшой огрызок
+ от контента. Да, у меня не больно какой-то великий контент, за который стоит
+ трястись, но я всё равно предпочту сохранить за собой все права на него.
+* В-третьих, **за пользователем остаётся право** выбирать где ему удобнее
+ следить за контентом. Либо на первоисточнике, с помощью божественного RSS (к
+ чему я бы хотел призывать), либо на удобной платформе куда происходит
+ синдикация.
+* В-четвёртых, ... А давайте, я не буду пересказывать вот эту статью[1]? 😉 В
+ общем, это правильная и нужная практика. Как минимум, на долгосрок. Платформы
+ приходят и уходят, а файлы (в виде markdown моего блога) останутся на всегда.
+
+=> https://indieweb.org/POSSE [1]
+
+## Что я сделал чтобы следовать POSSE?
+
+Ну для начала, у меня сильно чесались руки переделать дизайн блога. Вроде,
+получилось так, как я и хотел, в стиле сайтов начала-середины 2010х. Просто
+потому что могу, кто же мне тут что запретит 😉. Тем самым я улучшил UX блога,
+до хотя бы терпимого. Походу дела, при редизайне, я порасставил правильных тегов
+и микроформатов для правильной синдикации с другими платформами.
+
+Далее, я перепилил немного улучшил программку, которую написал уже достаточно
+давно, которая читает RSS моего блога и отправляет новые посты в Телеграм канал.
+Вот она, если что: [2]
+
+=> https://git.neonxp.ru/posse [2]
+
+Кстати, в очередной раз напоминаю о RSS ленте [3] блога. Эта лента — это самый
+правильный способ подписки на блог!
+
+=> https://neonxp.ru/feed/ [3]
+
+Так же из этой ленты автоматически подтягиваются посты в VK группу. Это сделано
+встроенным механизмом VK, за что им определенно респект! Не часто можно
+встретить нечто подобное на закрытых платформах (помним, же как Google убивал
+RSS?)!
+
+Так же в ближайших планах и запилить WebMentions и прочие плюшки с ИндиВеба.
+
+Ну пока, как то так `¯\_(ツ)_/¯`
+
+Есть что сказать? Внизу есть форма для невозбранного комментирования.
+
+## Ссылки по теме
+
+- https://indieweb.org/POSSE
+- https://www.theverge.com/2023/10/23/23928550/posse-posting-activitypub-standard-twitter-tumblr-mastodon
diff --git a/content/posts/2024-12-17-infra.md b/content/posts/2024-12-17-infra.md
new file mode 100644
index 0000000..8998c27
--- /dev/null
+++ b/content/posts/2024-12-17-infra.md
@@ -0,0 +1,120 @@
+---
+categories:
+- Мета
+date: '2024-12-17T21:07:53+03:00'
+description: ''
+draft: true
+image: files/2024-12-17-infra_cover.webp
+location: Казань
+tags:
+- блог
+- IT
+title: Инфраструктура блога
+---
+
+Сегодня я хочу рассказать как устроен этот блог и вообще моя инфраструктура.
+
+## Сервер
+
+Во-первых, недавно я почти полностью переехал с арендуемого сервера, на свой
+собственный, сервер, который просто стоит у меня в комнате.
+
+Именно он вынесен в заголовочное изображение и целиком помещается, даже не на
+ладони, а просто на кончиках пальцев!
+
+Конкретно, железо:
+
+* **OrangePi 3B 8Gb** — выбран в первую очередь за свою дешевизну и, самое
+ главное, M.2 разъём
+* **NVME SSD 1Tb** — собственно, жесткий диск моего микросервера
+* **Корпус с активным охлаждением** — не самое необходимое, но хотелось, чтобы
+ выглядело красиво
+
+<!-- more -->
+
+## Программное обеспечение
+
+По сути, на первом уровне, установлены armbian
+(https://www.armbian.com/orangepi3b/), веб—сервер Caddy
+(https://caddyserver.com/), да Docker. Всё остальное уже внутри Docker'а.
+
+
+## Caddy
+
+Caddy у меня работает в основном как reverse-proxy для Docker'а. Без лишних
+слов, вот конфиг:
+
+```
+{
+ log {
+ output file /var/log/caddy/access.log
+ level debug
+ }
+ email i@neonxp.ru
+}
+neonxp.ru:80 {
+ redir https://neonxp.ru
+}
+neonxp.ru:443 {
+ tls i@neonxp.ru
+ root * /var/www/neonxp.ru
+ encode gzip
+ rewrite /feed/ /posts/index.xml
+ file_server
+}
+comments.neonxp.ru {
+ reverse_proxy localhost:8008
+ tls i@neonxp.ru
+}
+```
+
+Из него я убрал всё, что не относится к непосредственно блогу.
+
+Сам блог у меня собирается с помощью Hugo и загружается в `/var/www/neonxp.ru` с
+помощью rsync[^4], а оттуда уже раздается с помощью Caddy.
+
+[^4]: https://git.neonxp.ru/blog.git/tree/Makefile#n11
+
+## Docker
+
+А вот и мой compose в котором разворачивается остальная инфраструктура для блога
+
+```yaml
+services:
+ remark42:
+ image: umputun/remark42:latest
+ restart: unless-stopped
+ container_name: "remark42"
+ ports:
+ - 8008:8080
+ env_file: remark42.env
+ volumes:
+ - remark42:/srv/var
+ posse:
+ image: registry.neonxp.ru/posse
+ restart: unless-stopped
+ container_name: posse
+ env_file: posse.env
+ volumes:
+ - ./seq.txt:/store/seq.txt
+volumes:
+ remark42:
+```
+
+Как понятно из этого docker-compose.yml — дополнительно поднимаются два
+контейнера:
+
+* remark42 — система комментариев
+* posse — моя программка, которая чекает RSS блога и репостит его в Telegram
+
+## Остальное
+
+Конечно же, на этой железке крутится не только блог, но и несколько других
+сервисов для личного использования
+
+* Nextcloud — личное облако
+* Vaultwarden — хранилище паролей
+* SOPDS — личная библиотека Либрусека
+* Git хостинг и Container registry — для разработки и хранения кода
+
+Но об этом я расскажу в другой раз 😉
diff --git a/content/posts/2024-12-30-irc.md b/content/posts/2024-12-30-irc.md
new file mode 100644
index 0000000..d58ecf6
--- /dev/null
+++ b/content/posts/2024-12-30-irc.md
@@ -0,0 +1,75 @@
+---
+categories:
+- Заметка
+date: '2024-12-30T14:54:08+03:00'
+description: ''
+draft: true
+image: files/2024-12-30-irc_logo.webp
+location: Казань
+tags:
+- IRC
+- IT
+title: IRC
+---
+
+Когда-то единственным способом общения в сети в режиме реального времени был
+исключительнольно протокол IRC. И всем бы он был хорош — простой, лёгкий, может
+работать на чём угодно. Но времена изменились и мы погрязли во всяких
+телеграммах да вотсаппах (пока не запрещенные на территории России, к
+сожалению).
+
+Это грустно, но закономерно. Но делает ли это ИРКу плохой? Да нет конечно! И
+лично меня притягивают именно такие надёжные и простые вещи — открытые,
+текстовые протоколы, софт для которых можно написать чуть ли не на коленке для
+любого электрочайника.
+
+Например, даже на таких устройствах[^1], я вполне себе могу представить клиент к
+ИРКе, но не представлю клиента телеграма.
+[^1]: https://club.hugeping.ru/blog/IYMX9ZdAnn0dA1RBO5JH#IYMX9ZdAnn0dA1RBO5JH
+
+И недавно я обнаружил, что IRC не только не умер, но и развивается,
+осовременивается! Сейчас есть актуальная современная версия протокола
+[IRCv3](https://ircv3.net/), которая не потеряла былой простоты и
+интерперабельности!
+
+# Мой IRC
+
+Короче, не затягивая сильно, я запустил для теста небольшой свой сервачок, куда
+и приглашаю забежать на огонёк и посидеть в ламповой олдскульной атмосфере:
+
+В любом современном IRC клиенте:
+
+* Сервер: `irc.neonxp.ru`
+* Порт: `6667` текстовый, `6697` TLS
+* Кодировка: `utf8`
+
+Регистрация есть через NickServ но опциональная.
+
+# Чем он хорош?
+
+Ну помимо вышеуказанных простоты и интерперабельности протокола, можно выделить
+и то, что поскольку общение чисто текстовое, без всяких гифок, картинок и
+прочего. Казалось бы, это же скорее минус? А вот и не обязательно. В каком-то
+роде это мотивирует к конструктивному общению, когда надо хоть немного включать
+мозг и думать что писать. Таким обазом, повышается осмысленность общения и
+появляется определённая самодисциплина. Примерно так же, как и в переписке по
+e-mail, что я тоже весьма и весьма уважаю.
+
+# Станет ли оно популярным?
+
+Да нет, конечно! Это всегда будет исключительно нишевая гиковская игрушка. И это
+даже хорошо. Лампово. Так же как и обычные текстовые блоги, например. Но это не
+значит, что это не имеет право на жизнь.
+
+Ну и да, это одна из технологий, которые я отношу к тем, что пригодятся
+человечеству в случае кризисов.
+
+# Альтернативы?
+
+Самая хорошая альтернатива, что я вижу — это протокол Matrix, который выглядит
+как новомодный хипстерский IRC с JSON поверх HTTP(S). С моей точки зрения, у
+него есть серьёзные недостатки, но считаю, что он вполне себе займёт ту же нишу.
+
+Всякие телеграммы и прочее завязанное на конкртеного вендора я не рассматриваю
+как альтернативы. Да, они удобные, популярные, но мертворожденные, как
+технология.
diff --git a/content/posts/2024-12-31-new-year.md b/content/posts/2024-12-31-new-year.md
new file mode 100644
index 0000000..56e2891
--- /dev/null
+++ b/content/posts/2024-12-31-new-year.md
@@ -0,0 +1,50 @@
+---
+categories:
+- Без рубрики
+date: '2024-12-31T15:48:25+03:00'
+description: ''
+image: 2025.webp
+location: Казань
+tags:
+- разное
+title: С Новым Годом!
+---
+
+Ну что же, друзья, с наступающим!
+
+В этот день принято подводить итоги года. Ну и я подведу немного:
+
+- Поступил в институт брака. Раз уж нет классического высшего, что ещё остаётся то ;)
+- В аккурат под конец года разрешились проблемы на работе. Причем разрешились
+ настолько удачно, что я почти что жду окончания новогоднего отпуска, чтобы
+ скорее начались трудовыебудни.
+- Стал активно вести блог. Но всё равно не оставляет подспудное ощущение, что
+ уже стал надоедать этим тем, кто подписан. После каждого поста жду что кто-то
+ да отпишется :) Но мне нравится его вести, так что, уже не остановлюсь :)
+- Ездили с новоиспеченной супругой на Кавказ. Самое яркое — посетили
+ обсерваторию в Нижнем Архызе. Под впечатлением, купили по приезду настоящий
+ телескоп!
+- Начали строить свой домик в деревне. Но пока ещё до заселения далеко, вот
+ только окна поставили.
+
+Под катом приложу фоточки наиболее ярких моментов, пожалуй.
+
+<!--more-->
+
+* Институт брака
+ ![Институт брака](/posts/files/2024-12-31-new-year_img/1.webp "Институт брака")
+* Выхожу с работы
+ ![Выхожу с работы](/posts/files/2024-12-31-new-year_img/2.webp "Выхожу с работы")
+* Собаньки на Кавказе
+ ![Собаньки на Кавказе](/posts/files/2024-12-31-new-year_img/3.webp "Собаньки на Кавказе")
+* Своя личная обсерватория
+ ![Своя личная обсерватория](/posts/files/2024-12-31-new-year_img/4.webp "Своя личная обсерватория")
+* Домик в деревне
+ ![Домик в деревне](/posts/files/2024-12-31-new-year_img/5.webp "Домик в деревне")
+
+Вот как-то так :)
+
+А пока, возвращаемся к новогоднему столу и готовимся встретить наступающий 2025
+год!
+
+Надеюсь, всё у нас у всех будет хорошо в этом наступающем новом году!
diff --git a/content/posts/2025-04-05-tabs-or-spaces.md b/content/posts/2025-04-05-tabs-or-spaces.md
new file mode 100644
index 0000000..a44e052
--- /dev/null
+++ b/content/posts/2025-04-05-tabs-or-spaces.md
@@ -0,0 +1,400 @@
+---
+categories:
+- Размышления
+date: '2025-04-05T16:53:27+03:00'
+description: null
+image: null
+location: Казань
+tags:
+- размышления
+title: Табы или пробелы?
+---
+
+Так получилось, что с Нового Года я ничего в блог не писал. Тому причина в
+личной загруженности, и в не менее личной лени. Так же я делал некоторые
+эксперименты над самим блогом, потому что моё внутреннее чувство прекрасного не
+даёт мне просто остановиться и не трогать то, что работает.
+
+Но всё же, я чувствую внутреннюю потребность написать небольшую заметку с
+размышлениями, которые недавно приходили ко мне в голову.
+
+А связаны они с тем, что есть определённые догмы в индустрии, которые непонятно
+(ну или понятно) почему появились, и которым слепо следуют, хотя, как будто они
+уже не имеют смысла.
+
+<!--more-->
+
+## Вечный спор
+
+Для затравки, «вечный спор» табы или пробелы использовать в коде для отсутпов.
+Лично для меня здесь не то что выбор очевиден, для меня очевидно, что и самого
+выбора то нет. Конечно же, только табы! Отступ пробелами просто не имеет права
+на жизнь, и вот почему:
+
+* Во-первых, это просто какой-то костыль, использовать пробел не по назначению.
+ Наверное, не очень очевидно, но назначение пробела — это именно разделение
+ слов. Невероятно! А наначение таба — как раз таки форматирование отступа.
+ Давайте использовать инструменты по назначению!
+* Во-вторых, и самое главное, как по мне, это гибкость табуляции. Я, как
+ читающий код, волен сам выбирать размер отступа. Например, если у меня узкий
+ экран (смартфон, например) — я выберу отступ в 2 *визуальных* пробела.
+ Наоборот, если бы у меня было слабое зрение — я бы выбрал отступ в бо́льшее
+ число *визуальных* пробелов.
+* В-третьих, исходя из предыдущего пункта, я считаю, что использование именно
+ пробелов — это диктование автором исходника мне своей воли в виде своих
+ предпочтений (например, только 4 пробела, и никак иначе!). А какого чёрта? Это
+ буквально насилие! Зачем? Я считаю, это не допустимо. Пусть у каждого будет
+ возможность выбирать себе настройки отображения на *своей* машине под *свои*
+ вкусы, а не вкусы автора!
+* В-четвёртых, самое малозначительное — это то, что таб это 1 байт, а пробелов
+ обычно больше чем 1 байт (от 2 до 8). Я считаю этот аргумент малозначительным,
+ т.к. уж что что, а места на носителях информации нынче в достатке. Но тем не
+ менее, это один из аргументов!
+
+А что по аргументам за пробелы? Да нет их. Ну окей, предположим, что есть. Во
+многих кодстайлах (PEP-8, PSR итп) закреплены именно пробелы. Я не понимаю,
+почему, вроде как, умные люди которые эти стандарты придумывали так сделали.
+Возможно, привычка. Но является ли привычка каких-то людей аргументом? Наверное,
+нет. И самое грустное, что эти стандарты уже не поменять, ибо с их
+использованием *уже* написаны мегатонны кодов.
+
+Единственное, меня радует, что хотя бы в стандарте форматирования моего любимого
+языка Go этой откровенной чуши нет. В Go отступы приняты табами и только ими.
+
+Сразу скажу, я говорил только про отступы в начале строки, но не про отступы
+внутри строки, например, чтобы выстраивать значения подряд идущих констант в
+одну ровную колонку. Там, вроде как, пробелы вполне оправданы. Но это не точно.
+Я пока не решил для себя.
+
+Думаю, здесь насчёт табов и пробелов можно завершить. Если есть что накинуть —
+пишите письма, e-mail внизу страницы.
+
+## Вечный консенсус
+
+Про табы и пробелы была скорее затравочка. Там, как мне кажется, всё очевидно.
+Но есть менее очевидная, но как мне кажется очень родственная тема. Эта тема
+вызывает сильно меньше споров, т.к. вроде как в ней уже есть консенсус. Но этот
+консенсус ошибочен!
+
+А говорю я про форматирование длины строк! А именно, т.н. hard-wraps и
+soft-wraps. Если коротко, при hard-wraps в текст в точках переноса (например, на
+80 или 120 колонке) вставляются символ переноса строк (`\n`), при мягком
+переносе текст остается на одной строке, но выглядит так, как будто он разделен
+на несколько строк.
+
+А начну я с небольшой предыстории, как я к этому пришёл. Как я уже писал в
+начале, у меня есть постоянное шило в седалище, которое не даёт мне просто
+остановиться и использовать то, что работает, как минимум, в контексте этого
+блога. И из последнего куда я смотрел — протокол Gemini[1]. Разбирая его, меня
+сначала немного удивила его особенность, а именно:
+
+=> https://geminiprotocol.net/ [1]
+
+> Text in Gemtext documents is written using "long lines", i.e. you (or your
+> editor) shouldn't be inserting newline characters every 80 characters or so.
+> Instead, leave it up to the receiving Gemini client to wrap your lines to fit
+> the device's screen size and the user's preference. This way Gemtext content
+> looks good and is easy to read on desktop monitors, laptop screens, tablets
+> and smartphones.
+
+> Note that while Gemini clients will break up lines of text which are longer
+> than the user's screen, they will not join up lines which are shorter than the
+> user's screen, like would happen in Markdown, HTML or LaTeX. This means that,
+> e.g. "dot point" lists or poems with deliberately short lines will be
+> displayed correctly without the author having to do any extra work or the
+> client having to be any smarter in order to recognise and handle that kind of
+> content correctly.
+
+Сначала, я подумал, да это же нифига не удобно, что используются длинные строки,
+а не склеиваются разделённые одним переносом как в Markdown! Более того, это моё
+возмущение подогревалось тем, что я всё это время был сторонником как раз
+hard-wraps и форматировал что код, что markdown для блога по 80 или 120 колонке.
+Потому что так всегда и везде было принято. Но потом вчитавшись, я понял, что
+как раз таки «склеивание» Markdown это максимально неправильное поведение! Оно
+порождает такие минусы, как более сложный парсинг, который должен обрабатывать
+по разному один и два переноса строк, неочевидность, когда пишешь текст в
+редакторе, а отображается он совсем по другому, потенциальные ошибки, когда
+абзацы внезапно склеиваются, и т.п.
+
+При этом, парсинг Gemtext поразительно простой. В общем случае, достаточно
+парсить по строке, и не думать о предыдущем состоянии (относится текущая строка
+к предыдущему параграфу или таки нет). Единственное исключение —
+преформатированный текст, при парсинге которого надо помнить состояние. Но и это
+очень просто, достаточно держать единственный флаг который говорит, мы сейчас в
+нормальном состоянии или в состоянии преформатированного текста. И переключать
+этот флаг когда очередная строка начинается с *```*. Вообще, Gemtext кажется
+наиболее правильным и приятным для меня языком разметки. Наверное, я на него
+перейду. Но потом, сейчас нет времени.
+
+К чему я тут углубился в описание формата Gemtext? А вот к чему: только после
+прочтения спеки этого формата до меня сошло озарение, что использование длинных,
+а не обрезанных по 80 или 120 или ещё какую колонку более правильное не только
+для формата разметки, но и для обычного кода!
+
+И вот аргументы:
+
+* Во-первых, все редакторы кода поддерживают soft-wrap и каждый волен выставить
+ для своего личного редактора удобную ему длину строки, а не подчиняться
+ привычкам автора кода.
+* Во-вторых, за длину в 80 символов топят в основном старпёры что-то там
+ говорящие про терминалы шириной в 80 символов. Только и этот аргумент не
+ понятен. Когда вы в последнее время видели терминал в 80 символов? Не эмулятор
+ терминала, а именно сам терминал? Ну даже, хорошо, пусть будет этот терминал в
+ 80 символов. Но он что, не умеет переносить? Подозреваю, что может. И в чём
+ тогда проблема? Непонятно. Короче, требование в 80 символов (ну или более
+ современное в 120) выглядит как высосанное из пальца, потому что под ним нет
+ реальной основы кроме каких-то там исторических причин на доисторическом
+ железе.
+* В-третьих, см. пункт про насилие автора кода над читателем кода. Например,
+ опять таки, узкий монитор например. И на нём не soft-wrapped текст может
+ вызывать горизонтальную прокрутку. И это убого.
+* В-четвёртых, да, это усложняет парсинг. Это слабый аргумент, я знаю. Как
+ пример, правильный парсер Markdown (не буду тут бомбить про количество разных
+ стандартов Markdown) пишется не то чтобы очень просто. В это же время,
+ написать парсер Gemtext который полностью покроет спецификацию — дело максимум
+ часа-двух для любого, кто программирует больше, хотя бы, нескольких месяцев!
+
+В общем, как и в случае с табо-пробелами я не вижу ни одной достойной причины
+делать жесткие переносы строк по какой-то длине!
+
+Возможно, я что-то упустил — тоже можно по этому поводу поспорить со мной в
+электропочте. Возможно, я даже поменяю мнение, но наврядли.
+
+## Update 06.04.25
+
+Как я и просил, один хороший человек, Владислав
+(https://t.me/c/1331521959/2285), написал ответ. Прокомментирую его здесь:
+
+> Мне есть что сказать про ширину таба и 80 символов.
+
+> Аргумент про разную ширину таба работает слабо: многие стили предполагают его
+> фиксированную длину. Если ставить другой, то форматирование ломается.
+
+> Пример: ядро Linux, где ширина таба 8, и аргументы функций "плывут" при другой
+> ширине.
+
+Я не единожды видел этот аргумент, но он как раз и кажется мне слабым. Большая
+ли разница для читающего код, как именно он его видит:
+
+```
+// tabsize=2
+ func someFunc(
+ one,
+ two,
+ three,
+ )
+...
+ callOfSomeFunc = someFunc(
+ "one",
+ "two",
+ "three",
+ )
+```
+
+или так
+
+```
+// tabsize=4
+ func someFunc(
+ one,
+ two,
+ three,
+ )
+...
+ callOfSomeFunc = someFunc(
+ "one",
+ "two",
+ "three",
+ )
+```
+
+или даже так
+
+```
+// tabsize=8
+ func someFunc(
+ one,
+ two,
+ three,
+ )
+...
+ callOfSomeFunc = someFunc(
+ "one",
+ "two",
+ "three",
+ )
+```
+
+Кажется, что для 8 пробелов на таб всё сильно уезжает, но раз человек себе так
+настроил — то как будто его право и наверное были основания?
+
+
+> Про 80 символов. Дело вообще не в размере терминала или ширине перфокарты.
+> Некоторые программисты разделяют редактор на две вкладки, чтобы смотреть два
+> файла.
+
+И тогда soft-wrap как раз и вместит весь код в каждую из половинок без
+горизонтальной прокрутки, о чём я и говорю.
+
+> Некоторые используют большой шрифт. С шириной в 120 символов мы лишаем из
+> возможности удобно читать код. К тому же, я считаю этот аргумент важным, 120
+> символов - это способ замаскировать плохой код. Чувак сделал 5 уровней
+> вложенности в коде? Отлично! Главное чтобы в 120 символов влезло.
+
+Всё так! Возможно, я не очень подробно расписал, но основная моя мысль в том,
+что такое жесткое ограничение мне кажется просто надуманным и взятым с потолка.
+А если я после функции хочу написать небольшой коммент и он ну никак не влезает
+на пяток символов? Новую строку ради этого делать? Ну как-то бредово. А для
+указанного случая гораздо лучше бы звучало ограничение в стандарте типа «не
+используйте больше 3 уровней вложенности в коде». Это хотя бы имело вполне себе
+обоснование, то что скорее всего такой код просто архитектурно неверен и его
+стоит пересмотреть.
+
+> Конечно, можно сказать что есть длинные константы или имена функций, но этот
+> спор становится менее однозначным. Как по мне вполне хороший консенсус - это
+> 100 символов в строке
+
+Здесь не согласен. Здесь опять «магическая константа» с потолка.
+
+> В целом, эти срачи мне кажутся достаточно поверхностными. Они в своем корне
+> несут вопрос "как повысить читаемость кода?", но акцентируются на мелочах.
+
+Согласен. Мелочи. Но почему и бы про мелочи не поговорить :) Из них по
+отдельности всё и строится (избитая фраза, да). В больших стандартах обычно
+говорится просто декларативно «только пробелы, отступ 4 пробела, длина строк
+120» и всё. А зачем и почему — опускается, как будто всем всё и так понятно. Мне
+вот не очень. Чувствую себя ребёнком спрашивающим «Почему небо синее?». Потому
+что мне кажется, что под этим требованием нет объективного требования кроме «так
+принято». А «так принято» я часто и принимаю как валидный аргумент, например,
+когда прихожу в какой-то проект, но в сути своей аргументом не является.
+
+> Хотелось бы иметь какие-то объективные метрики, какая-то работа в этом
+> направлении была проделана, но, как я понял, это, во-первых, недостаточно
+> точные метрики, а во-вторых, недостаточно развитая история.
+> https://seeinglogic.com/posts/visual-readability-patterns/
+
+Интересная статья, спасибо, с удовольствием прочитал. В целом, по выводам
+(https://seeinglogic.com/posts/visual-readability-patterns/#8-patterns-for-improving-code-readability)
+согласен. Метрика по Хольстеду (или как это перевести?) выглядит интересно, тем
+что она чётко считается (хотя когда я руками считал, что-то у меня не сошлось с
+примером :) ).
+
+Из объективных метрик, тут вскользь ещё упоминалась цикломатическая сложность,
+которая вполне себе имеет право на жизнь.
+
+А так же, только что пришло в голову что можно читабельность кода оценивать как
+вторую (?) производную от отступов по непустым строкам. При этом, чем эта
+производная ближе к нулю — тем лучше.
+
+То есть, грубо говоря вот такой «код»:
+
+```
+_____
+ ________
+ _____
+ _______
+ ___
+ ___
+ _____
+ __
+ ____
+___
+```
+
+Лучше чем, такой:
+
+```
+_____
+ ________
+ _____
+ _______
+ ___
+ ___
+ _____
+ __
+ ____
+ ___
+```
+
+Это стоит ещё подумать, это буквально пришло в голову только что, пока читал
+статью.
+
+P.S.: Из забавного
+
+> As others have written, computers are fast and premature optimization is a bad
+> thing.
+
+Сначала они пишут «computers are fast» а потом происходит такое: [2]
+
+=> https://tonsky.me/blog/disenchantment/ru/ [2]
+
+
+## Update 06.04.25 - 2
+
+Со вчерашнего дня я решил дополнить немного ещё.
+
+Во-первых, хочу немного снизить градус холиворности и радикальности. Ещё раз
+упомяну что не вижу проблем для выравнивания пробелами текста внутри строки. То
+есть например, вот так:
+
+```
+→ → ConstWithLongName = 0
+→ → Const1 = 1
+→ → Const2 = 2
+→ → Const3 = 3
+```
+
+для меня вполне нормально кажется. Даже более того, табы *внутри* строки кажутся
+плохим решением. Я говорю только про отступы в начале строки.
+
+Во-вторых, насчёт длинных строк. Я расписал немного сумбурно и в одну кашу
+смешал как код, так и просто текст. Не стоило так. Хоть это и разные сущности,
+но я всё равно считаю жесткое ограничение необоснованным ни там ни там. Но по
+разным причинам:
+
+* Для обычного текста ограничение в N символов выглядит таким же не обоснованым,
+ как, например, требование автора «Читайте мои тексты только шрифтом Arial
+ 12pt». Глупость? Глупость.
+* Так же встречал, что люди используют это ограничение при написании электронных
+ писем. Это выглядит как минимум странно. Письмо пишется для кого? Для
+ получателя, т.е. читателя. Почему отправитель за читателя решает то, как у
+ него будет отображаться письмо? Я часто читаю почту со смартфона с узким
+ экраном, но средним шрифтом (чтобы меньше напрягать глаза). И горизонтальная
+ прокрутка выглядит не очень. Горизонтальная прокрутка вообще почти всегда
+ выглядит не очень и её стоит избегать всеми силами.
+* Для кода же история другая. Я не настолько поехал чтобы требовать всё писать в
+ одну строку. Если у функции в сигнатуре много (больше одного - двух)
+ аргументов — то это отличная идея написать их в столбик, а не в длинную линию,
+ которая ещё неизвестно как перенесётся. Я против именно переноса только из-за
+ магической константы колиечества символов.
+
+Да и вообще я ни от кого ничего не требовал. Я предлагаю только задуматься, а
+обоснованны ли «общепринятые» вещи? Может, уже прошло какое-то время и ситуация
+поменялась и удобнее и эффективнее выбрать что-то другое?
+
+И как будто стоит абстрактному «читателю», к которому я отсылал, в этом посте,
+решать этот вопрос техническими средствами, типа editorconfig + pre-commit хуки
+на форматирование в принятый в команде формат? Возможно да. Иначе получится, что
+борясь за личную свободу — нарушаешь чужую свободу <del>писать говнокод</del>.
+
+А .editorconfig я себе такой в home положил:
+
+```.editorconfig
+[*]
+indent_style = tab
+tab_width = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+soft_wrap = true
+
+[*.{yml,yaml}]
+indent_style = space
+indent_size = 2
+
+[*.json]
+indent_size = 2
+```
+
+Вроде как, покрывает основное.
diff --git a/content/posts/2025-05-19-nxpcms-2.md b/content/posts/2025-05-19-nxpcms-2.md
new file mode 100644
index 0000000..b92a308
--- /dev/null
+++ b/content/posts/2025-05-19-nxpcms-2.md
@@ -0,0 +1,19 @@
+---
+date: '2025-05-19T01:00:00+03:00'
+title: NXPCMS — моя CMS'ка (ч.2)
+---
+
+Всё же, не могу не поделиться, какое же это счастье, когда пользуешься своим же
+самописным софтом!
+
+С одной стороны, конечно, когда видишь косячки — понимаешь, что это именно ты
+продолбался, и тебе это решать. А с другой стороны, полный контроль и ты
+понимаешь, _что_ пошло не так и _как_ это чинить!
+
+В дополнение предыдущего поста, про принципы системы, хочу добавить то, что
+сознательно не буду внедрять ни теги, ни категории. Вместо этого — обычное
+дерево файлов + в ближайшем плане полнотекстовый поиск по материалам сайта.
+А теги мне так и так казались какой-то порочной фигнёй.
+
+Вот и поделился радостью. Своя CMS располагает к тому, чтобы писать сюда больше
+и чаще ;)
diff --git a/content/posts/2025-05-19-nxpcms.md b/content/posts/2025-05-19-nxpcms.md
new file mode 100644
index 0000000..d1d30e6
--- /dev/null
+++ b/content/posts/2025-05-19-nxpcms.md
@@ -0,0 +1,77 @@
+---
+date: '2025-05-19T00:00:00+03:00'
+title: NXPCMS — моя CMS'ка (ч.1)
+---
+
+Долгое время я пользовался Hugo (а одно время, даже WordPress!). И в целом, всем
+он меня устраивал. Но недавно, произошло, казалось бы не связанное. Я снова
+воспылал интересом к треккерной музыке. При этом я немного полазал по сети,
+поспрашивал знакомых и собрал достаточно большую (>80Гб!) коллекцию. Но просто
+хранить на диске было скучно и я решил её выложить во внешку. Так появился
+shelter.neonxp.ru (сейчас не работает, почему - объясню ниже).
+
+Сначала список файлов сервил в веб просто Caddy, но у него был недостаток: он не
+мог дать послушать треккерный файл без скачивания. Тогда я накидал простенькую
+программку, которая так же просто отдаёт содержимое директории, но позволяла
+слушать треккерную музыку. Через некоторое время я подумал, а почему бы не
+прикрутить к ней и предпросмотр и других файлов? Сказано-сделано. Прикрутил
+сначала просмотрщик markdown и txt файлов. Дальше, мысль полетела уже по
+накатанной, и подумалось мне, что это же простенькая CMS. В эту сторону проектик
+я и стал развивать. И вот вчера я таки перевёл этот сайт на мою собственную CMS!
+
+Но пишу только сейчас, потому что вчера после переезда уже ничего не хотел
+писать, ибо переезд был непрост. И как я уже выше говорил, пока отключил shelter
+в пользу этого сайта. Позже заведу и shelter. Особенности моей CMS:
+* минимум конфигурации: один бинарник, который при запуске сервит сайт из
+ текущей рабочей директории.
+* структура сайта ~= файловая структура, отсюда и листинг файлов на каждой
+ странице
+* нет какого-то общего файла конфигурации (аля /etc/...), вместо этого для
+ каждой директории можно создать свой файл .config.json (формат hjson, на самом
+ деле), который распространяет своё действие на текущую директорию, и на все
+ вложенные. Вложенные директории могут иметь свои конфиги, которые могут или
+ частично или полностью переопределять родительский конфиг. Например:
+```
+/var/www/neonxp.ru/.config.json
+
+{
+ "title": "NeonXP.log",
+ "description": "Личный сайт Go-разработчика из Казани",
+ "index": [ "index.gmi", "index.md", "index.txt"],
+ "url": "https://neonxp.ru/",
+}
+```
+а для директории постов важно, чтобы сортировка была в обратном порядке, поэтому
+её конфиг выглядит следующим образом:
+```
+/var/www/neonxp.ru/posts/.config.json
+
+{
+ "description": "Блог", # <- перезапись родительского конфига
+ "desc": true,
+}
+```
+Немного напоминает дедушку Apache2 с его .htaccess :) Но мне это кажется весьма
+удачной идеей.
+* Основной формат разметки — gemtext. Просто потому что мне он нравится своим
+ радикальным минимализмом. Минималистично настолько, что его парсер в html для
+ этой CMS я написал примерно за час с нуля.
+
+На самом деле, написать свою CMSку — достаточно старая мечта, и в своём прошлом,
+я неоднократно это делал, ещё на PHP (ну тогда это было модно). Ну и кто мне
+запретит сделать это сейчас, с теми идеями что я указал выше?) По факту
+получилось что-то среднее между веб-сервером аля Apache2 и классическими CMS, и
+мне это нравится.
+
+## Что дальше?
+
+А дальше я буду развивать её в сторону тех фич, что нужны лично мне:
+* Доделать миграцию постов и материалов из старого блога. Сейчас всё
+ импортировано в автоматическом режиме и выглядит откровенно плохо
+* Поддержка предпросмотра большего числа форматов файлов
+* Хотелось бы сделать Basic авторизацию + загрузку файлов по http
+* Раз уж используется gemtext — сделать и поддержку gemini протокола
+* Прикрутить cgi или скрипты на lua? А почему-бы и нет? :) Хотя бы сделаю
+ какую-нибудь олдскульную гостевуху
+* Прикрутить все эти клёвые indieweb штуки, которые было весьма проблемно
+ прикрутить к Hugo блогу в силу его статичности
diff --git a/content/posts/2025-06-08-my-setup.md b/content/posts/2025-06-08-my-setup.md
new file mode 100644
index 0000000..a4b6dc7
--- /dev/null
+++ b/content/posts/2025-06-08-my-setup.md
@@ -0,0 +1,31 @@
+---
+date: '2025-06-08T00:00:00+03:00'
+tags:
+- сетап
+- гиковское
+title: Мой сетап 2025
+---
+
+Давно ничего не писал, да и не было особо о чём. Немного играюсь с нейросетями и
+LLM в последнее время. Если выйдет что интересное - напишу об этом.
+
+А пока хотел написать вот о чём.
+
+Не помню, чтобы я когда-либо писал о том, какой у меня основной сетап, хотя сам
+с удовольствием читал о том, как он организован у других людей. Пожалуй, пришло
+время и мне его описать.
+
+<!--more-->
+
+- [Ноутбук](/pages/setup/laptop/)
+- [Смартфон](/pages/setup/pda/)
+- [NAS](/pages/setup/nas/)
+
+# Окончание
+
+Это всё что я вспомнил так сходу. По-любому, я что-то забыл, поэтому пост будет
+дополняться.
+
+# UPD [14.06.2025]
+
+[Пост переехал в постоянный раздел](/pages/setup/)
diff --git a/content/posts/2025-08-02-meshtastic.md b/content/posts/2025-08-02-meshtastic.md
new file mode 100644
index 0000000..fad4394
--- /dev/null
+++ b/content/posts/2025-08-02-meshtastic.md
@@ -0,0 +1,66 @@
+---
+cover: /posts/files/meshtastic_img/tbeam.webp
+date: '2025-08-02T18:00:00+03:00'
+tags:
+- meshatastic
+- гиковское
+title: Meshtastic
+---
+
+Некоторое время назад наткнулся в блоге [Евгения
+Степанищева](https://bolknote.ru/all/myshastik/) на потрясающую штуку -
+[Meshtastic](https://meshtastic.org/), или как в народе её называют -
+«мышастик». Вкратце, это протокол и, в первую очередь, специальная прошивка для
+целого спектра устройств, которые позволяют организовать
+[mesh](https://ru.ruwiki.ru/wiki/%D0%AF%D1%87%D0%B5%D0%B8%D1%81%D1%82%D0%B0%D1%8F_%D1%82%D0%BE%D0%BF%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F)
+сеть поверх протокола [LoRa](https://ru.ruwiki.ru/wiki/LoRa).
+
+<!--more-->
+
+Базово, как это выглядит при использовании:
+
+У каждого участника сети есть небольшое портативное радиоустройство (готовое или
+самодельное - не важно), прошитое специальной прошивкой. К устройству, чаще
+всего (но необязательно!) подключен смартфон со специальным одноимённым
+приложением. Устройство ищет и старается подключиться к другим подобным
+устройствам, а со смартфона можно писать как в публичные, так и в приватные
+каналы. Но только текстом, т.к. скорость исчисляется байтами в секунду. При
+этом, хоть и радиус каждого устройства достаточно небольшой, но засчёт ячеистой
+топологии можно передавать сообщения не только тем, кто в радиусе приёма, но и
+тем, кого напрямую устройство «не видит», зато видят соседи или соседи соседей
+(в среднем, не больше 7 хопов обычно настраивают).
+
+Загоревшись, я тут же заказал себе
+[T-Beam](https://meshtastic.org/docs/hardware/devices/lilygo/tbeam/). Настройка
+оказалась до тупости простой, единственное, что надо учитывать, это то, какие
+частоты приняты в конкретном городе. Для Казани - это 868MHz.
+
+На своём 3D принтере напечатал ти-биму корпус, закрепил его на стену и оставил
+вылавливать ноды. За неделю накопилось почти 50 штук! Но при этом, я не скажу,
+что качество связи какое-то особо хорошее - регулярно теряются сообщения, да и
+единовременно у меня, по сути, только один аплинк до остальной сети. Ну ничего,
+я уже заказал антенну получше ;).
+
+![Мой T-Beam в напечатанном чехле](/posts/files/meshtastic_img/tbeam.webp)
+
+Чехол всратоватый потому что я его печатал в наихудшем качестве, на
+неотрегулированном принтере. Главное - свою задачу защиты выполняет.
+
+Понятное дело, что это просто игрушка и никакого практического применения у неё
+нет, а в основном чате - бо́льшую часть времени сплошные «Пинг, меня слышно?».
+Но! Очень забавная игрушка. И да, теоретически может позволить экстренно
+связаться там, где другой сети просто нет. Просто, это не мой случай. Хотя,
+недавно оказался в ситуации, когда и электричества дома не было (а с ним и
+домашней сети) и одновременно была БПЛА опасность, а значит, никакущая мобильная
+сеть. То есть, практически, идеальные условия для мышастика :).
+
+Несмотря на это, я всё же не удержался и заказал ещё два устройства, но в этот
+раз попроще. Брошу один в машину, а второй или супруге отдам, или в рюкзаке
+носить буду носить. Буду своими скромными силами хоть немного, да расширять сеть
+своими устройствами.
+
+Так же ездил на стройку своего будущего дома с устройством, но, ожидаемо, там
+оно сеть не увидело. Значит буду думать или о высокой антенне на крыше, или ещё
+что выдумывать, чтобы из своих пердей «дотянуться» до большой городской сети.
+
+А к чему я про него рассказываю? А просто так. Забавная гиковская штука.
diff --git a/content/posts/2025-08-05-lets-code-3d.md b/content/posts/2025-08-05-lets-code-3d.md
new file mode 100644
index 0000000..3809861
--- /dev/null
+++ b/content/posts/2025-08-05-lets-code-3d.md
@@ -0,0 +1,154 @@
+---
+cover: /posts/files/lets-code-3d_img/4.jpg
+date: '2025-08-05T20:00:00+03:00'
+tags:
+- 3D печать
+- гиковское
+title: Давай запрограммируем деталь?
+---
+
+Некоторое время назад я по глупости запорол свои бокорезы, так, что для
+откусывания ножек радио деталей они больше не годятся. К счастью, стоят они
+совершенно не дорого, и тем же днём были заказанные новые в небезызвестном
+сервисе, который в девичестве содержал в себе название моего родного города. При
+получении я немного огорчился тем, что в комплекте к ним не шёл колпачок,
+который был у предыдущих, хотя выглядят они идентично.
+
+Штош. Я решил восполнить этот недостаток с интересом для себя и решил этот
+колпачок самостоятельно спроектировать и напечатать на 3D принтере, который есть
+у меня на хозяйстве.
+
+<!--more-->
+
+![Бокорез и родной колпачок](/posts/files/lets-code-3d_img/1.jpg) Бокорезы и
+колпачок
+
+## Проектирование
+
+Обычно, детали для печати проектируются в CAD программах твердотельного
+моделирования. Но я в них совершенно не умею, хотя и сын
+инженеров-конструкторов. Но зато, к счастью, я программист. И вроде, не самый
+худший! Посему, я решил воспользоваться свободной программой для твердотельного
+моделирования [OpenSCAD](https://openscad.org/). К счастью, в репозитории
+любимого дистрибутива (как, в прочем, и в большинстве других репозиториев) он
+присутствовал. Что же в нём необычного? А необычное в нём то, что деталь в нём
+не _рисуется_, а именно что _программируется_. Для понимания, приведу простой
+пример:
+
+```openscad
+cube([25,35,55]); // Нарисовать куб размерами 25мм х 25мм х 55мм
+```
+
+Просто? Очень! А учитывая что язык полноценный, с циклами и условиями
+«напрограммировать» в нём можно многое.
+
+## Первая версия
+
+Вот и я не стал долго думать, открыл [мануал с
+оффсайта](https://openscad.org/documentation.html), обмерил штангенциркулем
+оригинальный колпачок и пошёл <del>проектировать</del> программировать. На всё
+про всё у меня ушло где-то с полчаса. И у меня получилась первая версия
+колпачка. Максимально простая и дубовая. Первая куцая версия:
+
+![Колпачок v1](/posts/files/lets-code-3d_img/2.jpg)
+
+## Вторая версия
+
+В принципе, на этом можно было и остановиться, ведь свою функцию он выполняет.
+Но у меня сработал мой перфекционист и я подумал, что было бы неплохо вырезы в
+корпусе сделать один под другим, а не на одной линии, чтобы бокорез сидел ровно,
+а не под углом. Потом пришла мысль, что было бы неплохо ещё и параметризировать
+модель, чтобы было легко менять её размеры, а не хардкодить их. И вот получилась
+вторая, и на текущий момент окончательная версия:
+
+![Колпачок v2 код](/posts/files/lets-code-3d_img/3.png)
+
+И результат «в железе», то есть в пластике :)
+
+![Колпачок v2 результат](/posts/files/lets-code-3d_img/4.jpg)
+
+А сам код, думаю, он достаточно понятен ([исходники](/posts/files/source.scad) и
+[STL модель](/posts/files/result.stl) я прикладываю к этому посту):
+
+```openscad
+// Толщина стенки
+wall = 2;
+
+// Высота внешняя
+height = 12;
+
+// Длина основной части (внутренняя)
+l1 = 15;
+
+// Длина носика (внутренняя)
+l2 = 20;
+
+// Ширина у основания (внутренняя)
+w = 15;
+
+// Толщина метала бокорезов
+toolWidth = 2.1;
+
+// Нижняя крышка
+cover(0);
+
+// Верхняя крышка
+cover(height - wall);
+
+// Корпус
+difference() {
+ linear_extrude(height)
+ polygon(
+ [ // Полигон идёт против часовой стрелки
+ [0, 0],
+ [wall, 0],
+ [wall, l1],
+ [w / 2 + wall, l1 + l2], // Внутренний кончик носика
+ [w + wall, l1],
+ [w + wall, 0],
+ [w + wall * 2, 0],
+ [w + wall * 2, l1],
+ [w / 2 + wall / 2 + wall, l1 + l2 + wall], // Внешний кончик носика
+ [w / 2 - wall / 2 + wall, l1 + l2 + wall],
+ [0, l1],
+ ]
+ );
+
+ // Вырезы
+ translate([w + wall, 0, height / 2 - toolWidth]) // Правый вырез чуть ниже середины
+ cube([wall, l1 / 2, toolWidth]);
+ translate([0, 0, height / 2]) // Левый вырез чуть выше середины
+ cube([wall, l1 / 2, toolWidth]);
+}
+
+
+// Крышка
+module cover(z) {
+ translate([0, 0, z])
+ linear_extrude(wall)
+ polygon(
+ [ // Полигон идёт против часовой стрелки
+ [0, 0],
+ [w + wall * 2, 0],
+ [w + wall * 2, l1],
+ [w / 2 + wall / 2 + wall, l1 + l2 + wall],
+ [w / 2 - wall / 2 + wall, l1 + l2 + wall],
+ [0, l1],
+ ]
+ );
+}
+```
+
+Надеюсь, я кому-то показал что даже без специального конструкторского
+образования, но умея программировать - можно получать не только эфимерные
+программки, но и вполне себе физические предметы, которые пригождаются в быту.
+И если интересно, подбиваю экономику: напечатано 2 колпачка (1 и 2 версия),
+каждый весом по 4 грамма, то есть примерно по 4₽ за штуку. Печатал пластиком
+PLA, как моим самым любимым.
+
+## Ссылки
+
+- Сайт OpenSCAD - https://openscad.org/
+- Документация - https://openscad.org/documentation.html
+- Шпаргала по функциям - https://openscad.org/cheatsheet/index.html
+- Библиотеки - https://openscad.org/libraries.html
diff --git a/content/posts/2025-08-09-makeup-organizer.md b/content/posts/2025-08-09-makeup-organizer.md
new file mode 100644
index 0000000..b6a3feb
--- /dev/null
+++ b/content/posts/2025-08-09-makeup-organizer.md
@@ -0,0 +1,38 @@
+---
+cover: /posts/files/makeup-organizer_img/2.png
+date: '2025-08-09T16:00:00+03:00'
+tags:
+- 3D печать
+- гиковское
+title: Ещё немного печати
+---
+
+Я продолжаю погружаться в печать не просто готовых моделей из интернета, что не
+очень интересно, но так же и в проектирование и печать собственных изделий.
+Сегодня моя любимая супруга попросила напечатать ей органайзер для косметики.
+Показала референс, какой она хочет. ТЗ понятное, размеры подобрали, осталось
+дело за малым — непосредственно запрограммировать изделие.
+
+<!--more-->
+
+На практике, это пока самое сложное по детализации изделие из всех, что я делал.
+Код получился кривоватым, но рисует то, что нужно.
+
+Вот так результат в редакторе:
+
+![результат в редакторе](/posts/files/makeup-organizer_img/1.png)
+
+А вот так в слайсере:
+
+![слайсер](/posts/files/makeup-organizer_img/2.png)
+
+Ну и впервые решил добавить «клеймо мастера» со своим логотипом. Впредь, буду
+добавлять его на все свои изделия, которые запроектированы именно мной.
+
+![клеймо мастера](/posts/files/makeup-organizer_img/3.png)
+
+Фотографий готового изделия пока нет, ибо печататься ему ещё минимум 10 часов.
+Фото я приложу позже отдельным постом ;)
+
+**P.S.** [архив с исходниками и готовым для печати STL
+прилагаю](/posts/files/organizer.tar.zst)
diff --git a/content/posts/2025-09-01-travel-1.md b/content/posts/2025-09-01-travel-1.md
new file mode 100644
index 0000000..7d86c1f
--- /dev/null
+++ b/content/posts/2025-09-01-travel-1.md
@@ -0,0 +1,142 @@
+---
+cover: /posts/files/2025-travel-1_img/preview_1.webp
+date: '2025-09-01T20:00:00+03:00'
+tags:
+- тревелблог
+title: 'Поездка по Кавказу. Часть 1: САО РАН и Аланское городище в Нижнем Архызе'
+---
+
+[![Дорога на
+САО](/posts/files/2025-travel-1_img/preview_1.webp)](/posts/files/2025-travel-1_img/1.webp)
+
+## САО РАН
+
+Первая настоящая вылазка за этот отпуск. В этот раз мы ездили снова на [САО РАН
+(Специальная Астрофизическая Обсерватория Российской Академии
+Наук)](https://ru.ruwiki.ru/wiki/Специальная_астрофизическая_обсерватория_РАН) в
+посёлке Нижний Архыз. В том году мы уже там бывали, но, во-первых, прикосновение
+к настоящей науке вдохновляет каждый раз, и я не знаю на какой бы раз мне туда
+надоело ездить. А, во-вторых, в этот раз был другой научный сотрудник, с
+совершенно другой лекцией, так что, скучать не пришлось! Кстати, этот телескоп —
+с самым большим в Евразии диаметром главного зеркала: аж 6 метров и весом в 42
+тонны! Высота купола — 53 метра, а высота самого телескопа в вертикальном
+положении (как на фото) — более 40 метров!
+
+![САО РАН](/posts/files/2025-travel-1_img/saologo.png)
+
+<!--more-->
+
+[Место на
+карте](https://yandex.ru/maps/?l=sat%2Cskl&ll=41.440447%2C43.646825&pt=41.4404472%2C43.6468250&z=14)
+
+Дальше — небольшой фотоотчёт и расскажу о втором месте, где побывал.
+
+[![Внезапная
+встреча](/posts/files/2025-travel-1_img/preview_2.webp)](/posts/files/2025-travel-1_img/2.webp)
+
+Внезапная встреча
+
+[![Стадо козочек и
+барашков](/posts/files/2025-travel-1_img/preview_3.webp)](/posts/files/2025-travel-1_img/3.webp)
+
+Стадо козочек и барашков
+
+[![Купол
+САО](/posts/files/2025-travel-1_img/preview_4.webp)](/posts/files/2025-travel-1_img/4.webp)
+
+Купол САО
+
+[![Обсерватория
+поменьше](/posts/files/2025-travel-1_img/preview_5.webp)](/posts/files/2025-travel-1_img/5.webp)
+
+Обсерватория поменьше
+
+[![Огромный
+кран](/posts/files/2025-travel-1_img/preview_6.webp)](/posts/files/2025-travel-1_img/6.webp)
+
+Огромный кран, с помощью которого устанавливается зеркало
+
+[![БТА](/posts/files/2025-travel-1_img/preview_7.webp)](/posts/files/2025-travel-1_img/7.webp)
+
+И вот он сам! Его величество — [БТА (Большой Телескоп
+Азимутальный)](<https://ru.ruwiki.ru/wiki/БТА_(телескоп)>)!
+
+Внезапно, в самый разгар лекции, купол начал поворачиваться! Видимо,
+предполагались какие-то профилактические работы перед очередной рабочей сменой
+обсерватории (работает она только по ночам и в ясную погоду, что логично). В
+копилку этой версии говорит и тот факт, что после поворота на главное зеркало
+поднялся специалист и проводил непонятные мне, дилетанту, работы. Возможно,
+ремонтно-профилактические.
+
+После экскурсии ещё смотрели на солнце (через специальный фильтр, конечно же!) и
+я впервые своими глазами увидел настоящие пятна на солнце!
+
+## Нижне-Архызское городище
+
+Сразу после САО мы поехали в [Нижне-Архызское
+городище](https://ru.ruwiki.ru/wiki/Нижне-Архызское_городище), где находятся
+руины храмов и других построек X-XII веков.
+
+[Место на
+карте](https://yandex.ru/maps/?ll=41.47500,43.68528&pt=41.47500,43.68528&spn=0.1,0.1&l=sat,skl)
+
+Поскольку, я не историк, и рассказать мне про них сверх данных из Рувики нечего
+— просто опубликую фотографии.
+
+### Средний храм
+
+[![Изображение
+8](/posts/files/2025-travel-1_img/preview_8.webp)](/posts/files/2025-travel-1_img/8.webp)
+
+[![Изображение
+9](/posts/files/2025-travel-1_img/preview_9.webp)](/posts/files/2025-travel-1_img/9.webp)
+
+[![Изображение
+10](/posts/files/2025-travel-1_img/preview_10.webp)](/posts/files/2025-travel-1_img/10.webp)
+
+[![Изображение
+11](/posts/files/2025-travel-1_img/preview_11.webp)](/posts/files/2025-travel-1_img/11.webp)
+
+[![Изображение
+20](/posts/files/2025-travel-1_img/preview_20.webp)](/posts/files/2025-travel-1_img/20.webp)
+
+### Северный храм
+
+[![Изображение
+17](/posts/files/2025-travel-1_img/preview_17.webp)](/posts/files/2025-travel-1_img/17.webp)
+
+[![Изображение
+12](/posts/files/2025-travel-1_img/preview_12.webp)](/posts/files/2025-travel-1_img/12.webp)
+
+[![Изображение
+13](/posts/files/2025-travel-1_img/preview_13.webp)](/posts/files/2025-travel-1_img/13.webp)
+
+[![Изображение
+14](/posts/files/2025-travel-1_img/preview_14.webp)](/posts/files/2025-travel-1_img/14.webp)
+
+[![Изображение
+15](/posts/files/2025-travel-1_img/preview_15.webp)](/posts/files/2025-travel-1_img/15.webp)
+
+[![Изображение
+16](/posts/files/2025-travel-1_img/preview_16.webp)](/posts/files/2025-travel-1_img/16.webp)
+
+### Солярный круг
+
+[![Изображение
+18](/posts/files/2025-travel-1_img/preview_18.webp)](/posts/files/2025-travel-1_img/18.webp)
+
+[![Изображение
+19](/posts/files/2025-travel-1_img/preview_19.webp)](/posts/files/2025-travel-1_img/19.webp)
+
+### Половецкая баба
+
+[![Изображение
+21](/posts/files/2025-travel-1_img/preview_21.webp)](/posts/files/2025-travel-1_img/21.webp)
+
+[![Изображение
+22](/posts/files/2025-travel-1_img/preview_22.webp)](/posts/files/2025-travel-1_img/22.webp)
+
+[![Изображение
+23](/posts/files/2025-travel-1_img/preview_23.webp)](/posts/files/2025-travel-1_img/23.webp)
+
+Продолжение поездки следует...
diff --git a/content/posts/2025-10-06-ai.md b/content/posts/2025-10-06-ai.md
new file mode 100644
index 0000000..d6b30e0
--- /dev/null
+++ b/content/posts/2025-10-06-ai.md
@@ -0,0 +1,124 @@
+---
+cover: /posts/files/hype_curve.png
+date: '2025-10-06T22:00:00Z'
+tags:
+- размышления
+- разное
+- ИИ
+title: Размышления о будущем ИИ
+---
+
+Немного моих размышлений про будущее ИИ как технологии, а не философии.
+
+Как водится, когда речь о размышлениях — буду сначала вводить тезисы, а потом,
+синтез.
+
+<!--more-->
+
+# Тезис первый
+
+Хоть сам и не пользуюсь таким инструментом, как Cursor из третьих рук я узнал,
+что там не просто внутри нейронка, а целый их ансамбль. Более того, что главное,
+там есть автовыбор того, какая именно БЯМ (большая языковая модель) будет
+отвечать за конкретный запрос. И вот этот момент меня заинтересовал. Я
+задумался, как именно это может быть реализовано. Первая мысль, как самая
+очевидная была в том, что внутри помимо больших моделей, есть и маленькая,
+единственная функция которой (а может и не единственная, но, значит, основная) —
+классифицировать запрос (окей, промпт) по тому, какая из больших моделей возьмёт
+работу на себя.
+
+Мысль эта мне показалась немного диковатой, т.к. показалось, что это несколько
+пушкой по воробьям, и подумал, что может есть другое, более классическое,
+алгоритмическое решение. Думал-думал, ничего хорошего в голову не пришло.
+Поэтому всё же взял за рабочую гипотезу, что таки да, классифицирует маленькая и
+дешевая моделька. Маленькая и дешёвая да, но точно не локальная. Ведь даже для
+маленьких моделек нужны определённые мощности, которые есть далеко не у всех.
+
+И вот здесь-то мысль и полетела дальше! Представилось что в будущем всё больше и
+больше «классические» алгоритмические задачи будут «закрывать» нейронками там
+где надо и не надо. Чем-то напоминает ситуацию в электронике, когда экономически
+часто более целесообразно не придумывать схему на элементарных радиодеталях, а
+воткнуть просто унифицированный микроконтроллер, который с правильной прошивкой
+заменяет целую кучку рассыпухи.
+
+Но продолжение мысли пока попридержу. А пока...
+
+# Тезис второй
+
+Я человек молодой, но более менее успел застать такой интересный переферийный
+девайс, как [математический
+сопроцессор](https://ru.ruwiki.ru/wiki/Математический_сопроцессор). Назначение
+устройства — позволять компьютеру выполнять операции над вещественными числами.
+Представляете, когда-то процессоры такие операции сами не умели выполнять! Но
+надо делать скидку на время и то, что такие операции были, наверное, не всем
+нужными, а скорее уделом специалистов, которые знали зачем им эта железка и
+целенаправленно её докупали. Сейчас любой, даже самый слабейший процессор имеет
+этот сопроцессор внутри себя.
+
+Ничего не напоминает?
+
+Нейронки и видеокарты!
+
+А вот сейчас, соединим тезисы и придём к ...
+
+# Синтез
+
+Нейронки, однозначно, вошли в нашу жизнь уже на долго.
+
+![Кривая хайпа](/posts/files/hype_curve.png)
+
+Мы сейчас, по моей оценке, где-то между пиком и дном, причём ближе именно к
+пику. Затем, неизбежно будет дно разочарований. Но это не столь важно сейчас.
+Важнее — следующий этап. Выйдет ли технология на плато продуктивности? Скорее
+всего, да. Всё же, помимо того, что это «ыыы прикольная штука», это ещё и вещь
+со вполне очевидной прикладной пользой. Не буду вдаваться в подробности, но я
+имею в виду обработку естественного языка (классификация, суммаризация,
+генерация), а так же «нечёткие» алгоритмы, когда путь к решению не задан заранее
+(агенты и всё такое).
+
+И по той причине, что мы ещё не на плато, есть ощущение некоего «дикого запада».
+Ещё не устаканились методы и подходы, практически ежедневно что-то появляется
+новое, а старое исчезает. Горизонт - буквально пара месяцев. И инструменты всё
+ещё крайне сырые. Под инструментами я имею в виду даже не программные
+инструменты, а то на чём это запускается. А запускаются нейронки на видеокартах!
+ВИДЕОкарты, Карл! Лично мне это выглядит несколько костыльным. Причём даже
+мощные профессиональные решения типа A100, H100 за миллионы рублей это по сути
+именно что видеокарты, хоть и без видеовыходов. Даже у криптовалют когда они
+были на хайпе и майнились на тех же несчастных видеокартах достаточно быстро
+появились специализированные решения в виде
+[айсиков](https://ru.ruwiki.ru/wiki/Интегральная_схема_специального_назначения).
+Ждут ли нас специализированные решения для задач ИИ? Конечно. Даже более того,
+они уже есть. У того же Huawei. Устройства, которые заточены только для нейронок
+и никак не способные в графику. И это, ИМХО, правильный путь, туда индустрия и
+пойдёт.
+
+Но это, опять таки, всё ещё настоящее. А что там в будущем?
+
+А в будущем, думаю, нас ждёт то, что специализированные устройства для инференса
+(запуска готовых моделей, грубо говоря) буду встроены непосредственно во все
+потребительские компьютеры и даже носимую технику, типа телефонов. Ровно так же,
+как было с математическими сопроцессорами, которые начинали отдельным
+устройством, а сейчас уже давно — просто небольшая часть на кристалле
+процессора. А применение им вижу как раз таки в том, чтобы там постоянно сидела
+небольшая (ну небольшая для того, будущего времени, для нас настоящих, скорее
+всего, весьма большая) моделька, к которой по вполне стандартизированным API ОС
+будет обращаться прикладной софт, чтобы выполнять какие-то свои прикладные
+задачи. Типа той, про которую я говорил в самом начале, по классификации того, в
+какую большую модель пойдёт запрос пользователя. Более того, кажется, что со
+временем это станет настолько общим местом, что многий софт и не запустится на
+железе без встроенного «интеллектуального сопроцессора». Как сейчас не
+запустится многий софт без, даже не математического сопроцессора (где вы найдете
+процессор без него?), а, например, каких нибудь SSE2 инструкций (я, честно
+говоря, не очень знаю зачем они, но, подозреваю, без них многое не заработает).
+А значит в этом самом будущем эти «интеллектуальные сопроцессоры» будут просто
+базовой частью любой ЭВМ (ну нравится мне эта аббревиатура).
+
+Да, можно возразить, что никак физически невозможно впихнуть такую
+вычислительную мощь в маленький кристалл на плате ноутбука, а, тем более,
+телефона. На это я отвечу просто: не знаю. Может быть и невозможно и такого не
+будет. Но ведь когда-то казалось невозможным что компьютер не будет занимать
+несколько комнат, а умещаться в кармане джинс каждого человека! Причём, тот что
+в джинсах, ещё и на много порядков будет мощнее! Так что я бы ничего не
+исключал.
+
+А как вы думаете?
diff --git a/content/posts/2025-10-11-blog.md b/content/posts/2025-10-11-blog.md
new file mode 100644
index 0000000..2a67127
--- /dev/null
+++ b/content/posts/2025-10-11-blog.md
@@ -0,0 +1,32 @@
+---
+date: '2025-10-11'
+draft: true
+tags:
+- блог
+title: Очередная смена движка блога
+---
+
+Ну не совсем так. Скорее сильно переделал NXPCMS. По сути, превратил её в
+статический генератор по типу Hugo. Только с максимальной поддержкой Obsidian.
+Как ссылок, так и его варианта Markdown.
+
+<!--more-->
+
+А раз изменения настолько кардинальные — не грешно и дать проекту новое имя.
+Встречайте: [YASSG. Yet Another Static Site
+Generator](https://gitverse.ru/neonxp/yassg). Оригинально, да.
+
+Далее сделал pipeline чтобы при коммите нового поста в git - автоматически блог
+собирался и деплоился. А этим постом я по сути проверю сейчас как это работает.
+
+Сейчас последовательность такая:
+
+1. пишу новый пост в Obsidian (достаточно удобно)
+2. Из него же с помощью специального Git плагина коммичу в репозиторий
+ (https://gitverse.ru/neonxp/sites)
+3. В этом репозитории запускается пайплайн который скачивает последний билд моей
+ новой CMS (https://gitverse.ru/neonxp/yassg/releases) и с помощью неё
+ собирает Obsidian Vault в статический сайт и деплоит его на сервер.
+4. Всё!
+
+Получается достаточно удобно!
diff --git a/content/posts/2025-10-18-the-ghost-in-the-machine.md b/content/posts/2025-10-18-the-ghost-in-the-machine.md
new file mode 100644
index 0000000..b8dff9f
--- /dev/null
+++ b/content/posts/2025-10-18-the-ghost-in-the-machine.md
@@ -0,0 +1,184 @@
+---
+cover: /posts/files/laughing-man.jpeg
+date: '2025-10-18'
+tags:
+- книги
+- размышления
+- не_моё
+- ИИ
+title: Душа в машине
+---
+
+Станислав Лем
+
+![Небольшая отсылка](/posts/files/laughing-man.jpeg)
+
+<!--more-->
+
+Понятием "душа в машине" - the ghost in the machine - некоторые психологи
+(английские) закрепляют убеждение в том, что человек якобы является существом
+"двойственным", т.е. состоящим из "материи" и "души".
+
+Сознание не является технологической проблемой, потому что конструктора не
+интересует, чувствует ли машина, а только интересует, действует ли она. Таким
+образом "технология сознания", как бы это сказать, может появиться только
+мимоходом, когда окажется, что определенный класс кибернетических машин обладает
+субъективным миром психических переживаний.
+
+Но каким образом можно узнать о наличии сознания в машине? Эта проблема имеет не
+только абстрактно-философское значение, ибо предположение, что какая-то машина,
+которая отправляется на лом из-за того, что ремонт не оплачивается, имеет
+сознание, превращает наше решение уничтожить материальный предмет, типа
+граммофона, в акт уничтожения индивидуальности, осознанного убийства. Кто-то мог
+бы оснастить граммофон пластинкой и выключателем таким образом, что, если бы мы
+сдвинули его с места, то услышали бы крики: "Ах, умоляю, подари мне жизнь!". Как
+можно отличить такой, без сомнения, бездушный аппарат от мыслящей машины? Только
+вступая с ней в разговор. Английский математик Аллан Тьюринг (Allan Turing) в
+своей работе "Может ли машина мыслить?" предлагает в качестве решающего критерия
+"игру в имитацию", которая основывается на том, что мы задаем Кому-то
+произвольные вопросы и на основании ответов должны сделать заключение, является
+ли этот Кто-то человеком или машиной. Если мы не сможем отличить машину от
+человека, то следует признать, что машина ведет себя как человек, то есть что
+она обладает сознанием.
+
+Отметим со своей стороны, что игру можно усложнить. Можно предположить два вида
+машин. Первый вид является "обычной" цифровой машиной, которая устроена как
+человеческий мозг; с ней можно играть в шахматы, разговаривать о книгах, о мире
+и вообще на все темы. Если бы мы ее вскрыли, то увидели бы огромное количество
+соединений элементов, подобно соединениям нейронов в мозгу, кроме того - блоки
+ее памяти и т.д. и т.п.
+
+Второй вид машины совсем другой. Это увеличенный до размера планеты (или
+космоса) граммофон. Она имеет очень много, например, сто триллионов, записанных
+ответов на всевозможные вопросы. Таким образом, когда мы спрашиваем, машина
+ничего "не понимает", а только форма вопроса, т.е. очередность вибраций нашего
+голоса, приводит в движение передатчик, который запускает пластинку или ленту с
+записанным ответом. Не будем задумываться о технической стороне вопроса.
+Понятно, что такая машина неэкономична, что ее никто не создаст, потому что это
+невозможно и главное - неизвестно, зачем ее создавать. Но нас интересует
+теоретическая сторона. Потому что, если вывод о том, имеет ли машина сознание,
+делается на основе поведения, а не внутреннего строения, не придем ли мы
+неосмотрительно к выводу, что "космический граммофон" обладает им - и тем самым
+выскажем нонсенс? (А скорее неправду).
+
+Можно ли, однако, запрограммировать все возможные вопросы? Без сомнения, средний
+человек не отвечает в обычной жизни даже на один их биллион. Мы же на всякий
+случай записали их во много раз больше. Что же делать? Мы должны вести нашу игру
+по достаточно развитой стратегии. Мы задаем машине (то есть Кому-то, потому что
+не знаем, с кем имеем дело; разговор ведется, например, по телефону) вопрос,
+любит ли она анекдоты. Машина отвечает, скажем, что да, она любит хорошие
+анекдоты. Рассказываем ей анекдот. Машина смеется (т.е смеется голос в трубке).
+Или у ней был этот анекдот записан и это позволило ей правильно отреагировать,
+т.е. засмеяться, или это в самом деле мыслящая машина (или человек, ибо мы этого
+не знаем). Мы разговариваем с машиной какое-то время, а потом неожиданно
+спрашиваем, припоминает ли она анекдот, который мы ей рассказали. Она должна его
+помнить, если она действительно мыслит. Она скажет, что помнит. Мы попросим,
+чтобы она повторила его своими словами. Вот это уже очень трудно
+запрограммировать, потому что таким образом мы вынуждаем конструктора
+"космограммофона" записать не только отдельные ответы на возможные вопросы, но и
+целые последовательности разговоров, которые могут вестись. Это требует,
+конечно, памяти, т.е. дисков или лент, которых, может, и вся солнечная система
+не вместит. Положим, машина не может повторить нашего анекдота. И мы тем самым
+разоблачаем, что она - граммофон. Задетый конструктор берется за
+усовершенствование машины таким образом, что пристраивает ей такую память,
+благодаря которой она сможет вкратце повторить сказанное. Но таким образом он
+сделал первый шаг в направлении от машины-граммофона к машине мыслящей. Так как
+бездушная машина не может признать идентичными вопросы аналогичного содержания,
+но сформулированные даже с незначительными формальными отклонениями, типа:
+"Вчера было хорошо на улице?", "Вчера была прекрасная погода?", "Погожим ли был
+предыдущий день?" и т. д. и т. п., то для машины бездушной они будут вопросами
+различными, а для машины мыслящей идентичными. Конструктор вновь разоблаченной
+машины вынужден опять ее перерабатывать. В конце концов, после долгой серии
+переделок, он введет в машину способности индукции и дедукции, способность
+ассоциации, схватывания тождественной "формы" по-разному сформулированных, но
+одинаковых по содержанию высказываний, пока в результате не получит машину,
+которая просто будет "обычной" мыслящей машиной.
+
+Так появляется интересная проблема: когда именно в машине появилось сознание?
+Предположим, что конструктор не переделывал эти машины, а относил каждую в музей
+и следующую модель создавал с начала. В музее стоит 10 000 машин, потому что
+столько было очередных моделей. Результатом стал плавный переход от "бездушного
+автомата" типа играющего шкафа к "машине, которая мыслит". Должны ли мы признать
+машиной, имеющей сознание, машину номер 7852 или только номер 9973? Они
+отличаются друг от друга тем, что первая не умела объяснить, почему она смеется
+над рассказанным анекдотом, а только говорила, что анекдот очень смешен, а
+вторая умела. Но некоторые люди смеются над шутками, хотя и не могут объяснить,
+что именно в них смешно, потому что, как известно, теория юмора - это твердый
+орешек. Разве эти люди тоже лишены сознания? Нет же, они, наверное, просто не
+очень быстро реагируют или малообразованные, их ум не обладает навыками
+аналитического подхода к проблемам; но мы спрашиваем не о том, умная ли машина
+или скорее туповатая, мы только спрашиваем, имеет ли она сознание или нет.
+
+Казалось бы, следует признать, что модель номер 1 имеет ноль сознания, модель
+номер 10 000 имеет полное сознание, а все средние имеют "все больше" сознания.
+Это утверждение показывает, насколько безнадежной является мысль о том, что
+сознание можно точно локализовать. Отсоединение отдельных элементов ("нейронов")
+машины спровоцирует только слабые, количественные изменения ("ослабления")
+сознания так же, как это делает в живом мозге прогрессирующий процесс болезни
+или нож хирурга. Проблема не имеет ничего общего ни с использованным для
+конструкции материалом, ни с размерами "мыслящего" устройства. Электрическую
+мыслящую машину можно построить из отдельных блоков, соответствующих, положим,
+мозговым извилинам. Теперь разделим эти блоки и разместим на всей Земле так, что
+один находится в Москве, второй в Париже, третий в Мельбурне, четвертый в
+Иокогаме и т. д. Отделенные друг от друга, эти блоки "психически мертвы", а
+соединенные (например, телефонными кабелями) они стали бы одной, интегральной
+"индивидуальностью", единым "мыслящим гомеостатом". Сознание такой машины
+находится ни в Москве, ни в Париже, ни в Иокогаме, но, в определенном смысле, в
+каждом из этих городов и, в определенном смысле, ни в одном из них. Потому что о
+нем трудно сказать, что оно, как Висла, имеет протяженность от Татр до
+Балтийского моря. Впрочем, подобная проблема демонстрирует, хотя и не так ярко,
+человеческий мозг, потому что кровеносные сосуды, белковые молекулы и ткани
+находятся внутри мозга, но не внутри сознания, и опять-таки нельзя сказать, что
+сознание находится под самым куполом черепа или, скорее всего, ниже, над ушами,
+по обеим сторонам головы. Оно "рассеяно" по всему гомеостату, по его
+функциональной сети. Ничего больше заявить на эту тему не получится, если мы
+хотим соединить сознание с возможностью рассуждать.
+
+Вышеприведенный текст, скопированный из моей "Суммы технологии", был написан в
+середине 1963 года. С точки зрения сегодняшней ситуации он представляет очень
+сильное упрощение дороги, которую мы должны пройти, чтобы дойти до имитации
+описанной мной цели. Мы уже предполагаем, что "сознание" и "интеллект" - это в
+определенном смысле отдельные сути бытия. Мы знаем, что существуют достаточно
+различные состояния сознания, даже если шкала их находится между сном и
+реальностью. Но и сон, точнее, мечта во сне, может характеризоваться
+разнообразной насыщенностью конкретностей, которые имитируют реальность,
+сознательно переживаемую наяву. В свою очередь, сознание наяву, что каждый знает
+по собственному опыту, даже если он не является ни психологом, ни психиатром,
+может также иметь очень различные состояния. Человек в состоянии болезненного
+жара может осознавать свое состояние, то есть то, что его сознание подверглось
+нарушению. Различные химические средства могут самым разным образом формировать
+человеческое сознание. Кроме того, следует отметить, что есть множество
+действий, которые человек может делать машинально, то есть четко и неосознанно.
+Сознание водителя автомобиля, особенно быстрого, "не успевает" за реакциями
+этого водителя в ситуациях с неожиданной последовательностью событий. Вместе с
+тем машинально можно делать глупости, мы называем их чаще всего "действиями по
+рассеянности".
+
+Все это я сказал в отношении моего текста тридцатипятилетней давности, в котором
+я задумался над "ростками" сознания в машине, и делал я это потому, что мне
+казалось, что люди очень отличаются друг от друга уровнем умственных
+способностей, а сознание всем дано приблизительно похожее.
+
+Дороги напрямик, по прямой и восходящей линии, от полного автомата, каким
+является компьютер, к машине, которой мы могли бы приписать сознание, нет.
+Вместе с тем работу нашего мозга мы уже знаем настолько, чтобы узнать то, что
+так называемая каллотомия, или рассечение большой белой спайки, соединяющей
+полушария мозга, не ликвидирует сознания, но создает в разделенных полушариях
+две его разновидности. Кроме того, мы знаем, что мозг является системой,
+построенной из огромного количества функциональных модулей, которые в отдельных
+окрестностях мозга создали среду, формирующую сознание. Уточню сказанное
+примером. Существует часть коры мозга, способствующая тому, что мы видим цвета.
+Повреждение этого модуля приводит к тому, что таким образом пораженный человек
+видит все без цвета, как в черно-белом кино. Чем точнее мы узнаем специфику
+функциональной ориентации модулей мозга, тем с большим удивлением узнаем, как, с
+точки зрения инженерной экономии, хаотично устроен мозг, хотя мы при осознании
+самих себя не отдаем себе в этом отчета. Сегодня нам кажется, что отдельные
+модули, функционально похожие на модули мозга, мы уже сможем конструировать.
+Обычно это псевдонейронные сети различной сложности. Вместе с тем мы еще не
+умеем ни создать их в достаточном количестве, ни соединить их таким образом,
+чтобы созданное произведение смогло имитировать сознание. Следовательно, прямой
+дороги от бездумного автомата к сознательно мыслящей машине нет. Есть, однако,
+много сложных дорог, которые в будущем приведут нас к цели и, может быть, эту
+цель превзойдут. О такой возможности я написал книгу "Голем XIV".
+
+Краков, 7 июля 1998 года.
diff --git a/content/posts/2025-11-03-blog-deploy.md b/content/posts/2025-11-03-blog-deploy.md
new file mode 100644
index 0000000..2b394d6
--- /dev/null
+++ b/content/posts/2025-11-03-blog-deploy.md
@@ -0,0 +1,46 @@
+---
+date: '2025-11-03'
+tags:
+- блог
+title: Деплой блога
+---
+
+А ещё, я решил поделиться тем как я пишу в блог. Потому что, почему бы и нет.
+
+<!--more-->
+
+Во-первых, у меня есть такой вот Makefile просто в корне home:
+
+```Makefile
+new-post:
+ @printf "Введите имя поста (латиницей, без пробелов) [new-post]: "; \
+ read postname; \
+ if [ -z "$$postname" ]; then \
+ postname="new-post"; \
+ fi; \
+ date=$$(date +%Y-%m-%d); \
+ file="neonxp.ru/posts/$$date-$$postname.md"; \
+ echo "---" > "$$file"; \
+ echo "title: " >> "$$file"; \
+ echo "date: $$date" >> "$$file"; \
+ echo "tags: []" >> "$$file"; \
+ echo "---" >> "$$file"; \
+ echo "" >> "$$file"; \
+ echo "---" >> "$$file"; \
+ echo "Комментариев в блоге не предусмотрено, но вы всегда можете написать мне на e-mail [i@neonxp.ru](mailto:i@neonxp.ru) или в джаббер [i@neonxp.ru](xmpp://i@neonxp.ru)" >> "$$file"; \
+ nvim "$$file"
+
+publish-post:
+ yassg generate
+ scp -r /home/neonxp/.local/share/yassg/* neonxp.ru:/var/www/neonxp.ru/
+```
+
+И, соответственно, когда я хочу написать новый пост, я вызываю `make new-post`,
+скрипт у меня спрашивает имя файла, а затем открывает любимый neovim, в котором
+я уже и пишу сам текст поста.
+
+Затем я вызываю `make publish-post` и сначала мой генератор статических сайтов
+[YASSG](http://gitverse.ru/neonxp/yassg/) собирает сайт в статический HTML, а
+потом отправляет всё на сервер в директорию, из которой сайт раздаётся.
+
+Очень просто!
diff --git a/content/posts/2025-11-03-my-setup.md b/content/posts/2025-11-03-my-setup.md
new file mode 100644
index 0000000..a375155
--- /dev/null
+++ b/content/posts/2025-11-03-my-setup.md
@@ -0,0 +1,21 @@
+---
+date: '2025-11-03'
+tags:
+- гиковское
+- сетап
+title: Обновления по сетапу
+---
+
+Со времени [последнего поста](/posts/2025-06-08-my-setup/) про мой
+[сетап](/pages/setup/laptop/) произошли достаточно серьезные изменения.
+
+<!--more-->
+
+Я перешёл таки с KDE сначала на Hyprland, а затем окончательно осел на Gnome (48
+на текущий момент). ОС осталась та же — AltLinux P11. Что что, а Alt меня
+всецело устраивает.
+
+Первое время с Гнома прям отплёвывался после KDE то, но потом то ли привык, то
+ли просто как-то проникся внутренней эстетикой, но, в общем, мне стало заходить.
+
+По железу обновлений, к сожалению, нет.
diff --git a/content/posts/2025-11-04-blog-deploy-2.md b/content/posts/2025-11-04-blog-deploy-2.md
new file mode 100644
index 0000000..b692d94
--- /dev/null
+++ b/content/posts/2025-11-04-blog-deploy-2.md
@@ -0,0 +1,50 @@
+---
+date: '2025-11-04'
+tags:
+- блог
+title: Деплой блога — пересмотр
+---
+
+После [вчерашнего поста](2025-11-03-blog-deploy) мне написал один [хороший
+человек](http://www.stargrave.org) с дельным замечанием, что не стоит для этих
+целей использовать make. Действительно так. И предложил хорошее решение, что
+это стоило сделать просто sh скриптами.
+
+У меня только один вопрос. А почему я сам-то так сначала не сделал? Это же
+буквально на поверхности!
+
+Штош, бывает, затупил. Да и привык для всех гвоздей использовать этот молоток.
+
+<!--more-->
+
+Обновлённые скрипты:
+
+~/.local/bin/new-post
+
+```sh
+#!/bin/sh -e
+postname="${@:-new-post}"
+date=$(date +%Y-%m-%d)
+fn="neonxp.ru/posts/$date-$postname.md"
+cat >$fn <<EOF
+---
+title:
+date: $date
+tags: []
+---
+
+---
+EOF
+$EDITOR $fn
+```
+
+~/.local/bin/deploy-blog
+
+```sh
+#!/bin/sh -e
+yassg generate
+scp -r /home/neonxp/.local/share/yassg/* neonxp.ru:/var/www/neonxp.ru/
+```
+
+Сейчас раздумываю, а почему бы этот функционал не включить в сам yassg,
+например, аналогом хуков? Надо будет обмозговать.
diff --git a/content/posts/2025-11-09-migration.md b/content/posts/2025-11-09-migration.md
new file mode 100644
index 0000000..aec1df0
--- /dev/null
+++ b/content/posts/2025-11-09-migration.md
@@ -0,0 +1,55 @@
+---
+comments: true
+date: '2025-11-09'
+tags:
+- блог
+- разное
+title: Переезд?
+---
+
+В последнее время всё больше и больше думаю, что у меня перебор серверных
+мощностей. Да и софтовое хозяйство там немного в бардаке по историческим
+причинам. Есть желание капитально прибраться.
+
+<!--more-->
+
+Сейчас у меня основная железка на хозяйстве это Intel Xeon E3-1230 3.2 ГГц, 16
+ГБ DDR3ECC, 4x1TB HDD SATA в виде арендуемого Dedicated сервера.
+
+И вроде неплохо, а вроде как и перебор. Зачем мне так много?
+
+У меня сейчас крутится несколько сайтов (ну по сути 0 нагрузки), Jabber сервер
+(Prosody), почта (MOX), Mumble сервер да DNS (CoreDNS).
+
+~~Кажется, что под всё это хватит и небольшой VDS'ки.~~
+
+UPD: посмотрел цены, в общем, выигрыша по деньгам особо не будет. VDS стоят
+как-то неадекватно дорого. Видимо, придётся остаться на текущем дедике, просто
+основательно почистить его, видимо.
+
+~~Переезд я буду делать, скорее всего, поэтапно:~~
+
+Переезд получится единомоментным, т.к. переезд будет на тоже самое железо, а не
+на другое.
+
+1. ~~Заведу VDS'ку.~~ Новую заводить не буду, а почищу начисто текущий сервер.
+2. Настрою почту (MOX хорош, конечно, но я хочу чего-то более кондового, типа
+ Postfix) - перекину на новый сервер MX записи.
+3. Настрою Jabber (Prosody, как был - так и останется, он достаточно хорош) -
+ перекину на него SRV записи.
+4. Так же естественно, надо не забыть Coturn сервер для звонков по Jabber.
+5. И только после этого надо переносить Git (Forgejo), и сайты. При этом
+ перееду с Caddy на Angie. Перекину уже A (и даже AAAA!) записи.
+6. Перенос CoreDNS.
+7. Проверяю что всё хорошо и если так, то отказываюсь от арендованного сервака.
+
+Надеюсь, ничего не забыл. А если и забыл — значит оно мне и не нужно, в
+общем-то.
+
+Что по итогу? Самое главное — сброшу груз старья, который накопился за годы
+аренды дедика. И что тоже приятно, но не главное — оптимизирую расходы. Причём,
+наверное, раз в 10.
+
+По времени — думаю, растянется на неделю-две, т.к. свободного времени на всё
+это у меня, как всегда, катастрофически мало. Так что, если какие-то мои
+сервисы будут не доступны — значит я в процессе переезда.
diff --git a/content/posts/2025-11-23-org.md b/content/posts/2025-11-23-org.md
new file mode 100644
index 0000000..95b54ee
--- /dev/null
+++ b/content/posts/2025-11-23-org.md
@@ -0,0 +1,253 @@
+---
+comments: true
+date: '2025-11-23'
+tags:
+- гиковское
+- моё
+title: Личный органайзер
+---
+
+Пришло в голову, почему бы не рассказать как у меня организован личный
+органайзер.
+
+Для начала стоит очертить то, какие у меня потребности от органайзера:
+
+- Вести список ежедневных, еженедельных, ежегодных, а так же, одноразовых
+ событий
+- Вести быстрый список ближайших задач (ToDo список). Под быстрым, я
+ подразумеваю то, что внести новый пункт в него я могу не дольше, чем за пару
+ десятков секунд. Если это будет требовать бо́льших усилий, то я себя знаю: я
+ это быстро заброшу, т.к. это станет для меня не помощью, а повинностью.
+- Место для быстрых заметок в формате «бесконечного текстовика». Аналогично,
+ это должно быть под рукой в быстром доступе. Obsidian себя показал _слишком_
+ медленным. Настолько, что мне стало проще запоминать, чем испытывать свои
+ нервы каждый раз, наблюдая его длительный запуск. Да, звучит на первый взгляд
+ глупо, но у меня так: запуск Obsidian длительностью в десяток секунд
+ окончательно отбил у меня желание вовсе запускать его.
+
+Что же делать? Искать идеальный для себя инструмент? Идеального для _себя_
+точно не найду. Написать самому, ведь «яжпрограммист»? Можно, но откровенно
+жаль время. Что же делать-то?
+
+<!--more-->
+
+К счастью, я вспомнил что у меня же unix-подобная операционная система, в
+поставке которой огромное количество небольших программ, которые прекрасно
+выполняют какую-то небольшую функцию и при этом отлично стыкуются друг с другом
+через стандартный текстовый поток! Грешно не воспользоваться наработками
+гораздо более умных, чем я, программистов!
+
+# ToDo
+
+Проще всего оказалось с этим. Просто поставил себе
+[todo.txt](https://github.com/todotxt/todo.txt-cli). Хоть я и говорил выше, что
+идеального инструмента я не найду, но я тогда немного слукавил. Для ToDo этот
+инструмент _почти_ идеален. Всё что мне надо, кроме одного нюанса, там есть. А
+вот тот самый нюанс, я когда-нибудь исправлю. Возможно.
+
+Для удобства я себе в zsh добавил следующие alias:
+
+```zsh
+alias t=todo.sh
+```
+
+Таким образом, чтобы добавить задачку я просто пишу `t add текст задачи`. Куда
+уж проще и быстрее?
+
+# Календарь
+
+Идею организации календаря я подсмотрел в программе calendar, которая идёт в
+комплекте с BSD системами, но не идёт в конкретно моей ОС. Да, наверняка, можно
+и к себе притащить, но я из спортивного интереса хотел решить задачу
+максимально встроенными и стандартными инструментами.
+
+Лонг стори шорт:
+
+~/calendar.txt
+
+```
+01-28 ДР Лены
+10-18 ДР Мамы
+05-24 Годовщина свадьбы
+11-23 Ежегодное событие
+
+Пн 15 Еженедельный мит
+Пн 20-21 Чтение
+
+Вт 13 Архком
+Вт 15 Грумминг
+Вт 20-21 Чтение
+
+Ср 13 Техразвитие
+Ср 20-21 Чтение
+
+Чт 15 Грумминг
+Чт 20-21 Чтение
+
+Пт 20-21 Чтение
+Сб 20-21 Чтение
+Вс 20-21 Чтение
+
+2025-11-19 10 клуб амбассадоров
+2025-11-17 16:30-17:30 Встреча c 16:30 до 17:30
+2025-11-24 10:15 Golang Техком
+2025-11-24 11-12 Анализ логики состояния
+2025-11-23 21 Написать в блог о своём календаре
+```
+
+Пояснения:
+
+- `mm-dd\t\tСобытие` - некие ежегодные события, у которых указаны только месяц
+ и день месяца
+- `Пн\tвремя\tСобытие` - еженедельное событие. Про формат времени - будет ниже.
+- `yyyy-mm-dd\tвремя\tсобытие` - разовые события в конкретную дату и время.
+
+Формат времени: его я подсмотрел у формата
+[calendar.txt](https://terokarvinen.com/2021/calendar-txt/), то есть, запись
+формата `15` - это означает что событие начнётся в 15 часов, `20-21` - событие
+длится с 20 до 21 часа вечера. С минутами, которые не обязательны, думаю, всё
+понятно из примера.
+
+Формат сам по себе абсолютно не жёсткий, допускает много вольностей. Главное,
+всё сводится к тому, что у него 1 строка - 1 событие и сама строка состоит из 3
+полей разделённых табом (в формате calendar.txt предлагается точка, для меня
+это показалось неприемлемым, т.к. я записываю в события и ссылки на созвоны, а
+ссылка включает в себя минимум одну точку)
+
+В принципе, тут уже можно было бы и остановиться и жить с просто текстовиком,
+но так было бы не интересно. Я написал на языке оболочки несколько полезных
+скриптов. Они настолько маленькие, что я просто приведу их здесь:
+
+~/.local/bin/calendar
+
+```sh
+#! /bin/sh
+
+cur=${2:-`date +%Y-%m-%d`}
+file=${1:-~/calendar.txt}
+grep \
+ -e "^$(date +%Y-%m-%d -d $cur)"\
+ -e "^$(date +%a -d $cur)"\
+ -e "^$(date +%m-%d -d $cur)" $file |\
+sort -n -k 2 |\
+cut -f2- | fold -w 80 -s
+```
+
+Собственно, это главный скрипт, который собирает для текущей (или явно
+указанной) даты все релевантные события, сортирует их по времени и выводит
+форматированным списком. Примерно так:
+
+```
+% calendar
+ Ежегодное событие
+20-21 Чтение
+21 Написать в блог о своём календаре
+```
+
+На этом я не остановился, но сделал ещё парочку вспомогательных скриптов,
+использующих его за основу:
+
+~/.local/bin/today
+
+```sh
+#!/bin/sh
+echo "Календарь:"
+echo -e ''$_{1..80}'\b-'
+cal
+echo -e ''$_{1..80}'\b-'
+echo "События дня:"
+echo -e ''$_{1..80}'\b-'
+
+calendar
+
+echo -e ''$_{1..80}'\b-'
+
+echo "ToDo:"
+todo.sh ls
+```
+
+Делает по сути тоже самое что и просто calendar, только ещё и рисует красивый
+графический календарик и показывает список ToDo задач.
+
+~/.local/bin/week
+
+```sh
+#!/bin/sh
+
+echo "На 7 дней:"
+for i in {0..6}
+do
+ d=`date +%Y-%m-%d -d "+ $i day"`
+ echo -e ''$_{1..80}'\b-'
+ echo $d
+ echo -e ''$_{1..80}'\b-'
+ calendar ~/calendar.txt $d
+done
+echo -e ''$_{1..80}'\b-'
+echo "ToDo:"
+todo.sh ls
+```
+
+Выводит план на 7 дней вперёд.
+
+## Редактирование календаря
+
+Здесь тоже предельно просто: добавил в zshrc такой алиас:
+
+```zsh
+alias ev='nvim +/`date +"%Y-%m-%d"` ~/calendar.txt'
+```
+
+и просто по команде `ev` открывается neovim и готов принимать новое событие.
+Хотя это и не самая частая операция.
+
+# Быстрые заметки
+
+Тут тоже всё просто:
+
+```zsh
+alias qn='nvim "+normal G" ~/quicknote.txt'
+```
+
+Соответственно, по команде `qn` открывается мой текстовик для заметок на самой
+последней строке. Можно дописать или поискать что-то с конца. На самом деле
+очень удобно!
+
+# Мобильный?
+
+Я бы хотел все эти мои текстовики иметь и на мобильном устройстве. Даже не для
+редактирования, а например, свериться со списком задач / событий.
+
+Тут чуть сложнее. Для синхронизации с мобильным устройством я сделал такой финт:
+
+1. Все текстовики у меня лежат не в домашней директории, на самом деле, а в
+ некой директории из которой симлинками уже прокинуты в корень домашней
+ директории.
+2. Директория эта добавлена в Syncthing который синхронизирует её с NAS и
+ мобильным устройством.
+3. На мобильном устройстве стоит замечательная программа
+ [Markor](https://f-droid.org/packages/net.gsantner.markor/) которая нативно
+ понимает формат todo.txt, ну и достаточно неплохо позволяет смотреть
+ редактировать файлы calendar.txt и quicknote.txt.
+
+# Чтобы хотелось ещё?
+
+- Как я упоминал, в todo.txt для меня есть неприятный нюанс который я бы хотел
+ исправить, а именно, вложенные задачи, когда у одной задачи может быть
+ сколько угодно дочерних, у которых, так же могут быть дочерние. Пока думаю,
+ расширить формат табуляцией в начале строки. Количество \t - уровень
+ вложенности. Но тогда придётся модифицировать todo.txt-cli который я
+ использую. И непонятно как это проглотит Markor. Можно конечно использовать
+ встроенную возможность задавать key-value значения. Тогда будет что-то типа
+ `Подзадача parent:2`. Это, как будто, самый правильный способ, который и
+ рекомендуется разработчиками формата, но получается слишком многословно, а
+ даже если сократить до `p:2` - всё равно надо в голове держать номер
+ родительской задачи. Так себе. Не знаю ещё как поступлю, но как-то поступлю.
+- Было бы неплохо прикрутить парсинг ICS файлов из почты для автоматического
+ добавления событий в календарь. Это сто́ит сделать однозначно!
+- Ну и очень желательно сделать скрипт который по крону за 15 минут до события
+ напомнит о нём, через какой-нибудь `notify-send`. Это, на самом деле, из всех
+ хотелок самая приоритетная для меня сейчас.
+
+Если будет интересно, я могу здесь рассказывать о том, что сделал из этих
+хотелок.
diff --git a/content/posts/2025-12-02-httpsocalypse.md b/content/posts/2025-12-02-httpsocalypse.md
new file mode 100644
index 0000000..f38fa36
--- /dev/null
+++ b/content/posts/2025-12-02-httpsocalypse.md
@@ -0,0 +1,31 @@
+---
+comments: true
+date: '2025-12-02'
+tags:
+- размышления
+title: HTTPS и конец интернетов
+---
+
+[Let's Encrypt уменьшит срок действия сертификатов до 45
+дней.](https://www.opennet.ru/opennews/art.shtml?num=64363)
+
+А потом будет на неделю, на день, на запрос... Короче, да, по сути амерская
+конторка будет решать на какой сайт будет возможно зайти, а на какой нет. А то
+что хромые браузеры сделают невозможным заход на сайты без валидного
+(заверенного _кем надо_, конечно же) сертификата, я уже и не сомневаюсь.
+
+Во истину говорю вам: грядут последние дни интернета. Ну точнее WWW, если
+говорить конкретнее, но это уже душнилово.
+
+<!--more-->
+
+Но это не сильно то печаль. Интернет, всё равно, для большинства уже скукожился
+до двух десятков «сервисов». А для энтузиастов будут «свободные» браузеры,
+свободные не только с точки зрения кода, но и политики стран НАТО.
+
+К счастью, у нас есть Librewolf, Palemoon, Dilo, да Lynx наконец. Без
+интернета (окей, WWW) не останемся.
+
+Так же можно рассмотреть и альтернативные технологии типа gopher, gemini. Но
+это, как-нибудь, в другой раз. А пока запасаемся попкорном и смотрим как горит
+<del>мир</del> интернет.
diff --git a/content/posts/2025-12-21-sicktech.md b/content/posts/2025-12-21-sicktech.md
new file mode 100644
index 0000000..6a7428c
--- /dev/null
+++ b/content/posts/2025-12-21-sicktech.md
@@ -0,0 +1,228 @@
+---
+comments: true
+cover: /posts/files/2025-12-21-sicktech.png
+date: '2025-12-21T18:40:26+03:00'
+tags:
+- it
+- размышления
+- sicktech
+- лонгрид
+title: Про здоровые и нездоровые технологии
+---
+
+Я заметил, что часто стал в речи употреблять словосочетания «здоровая
+технология» или «нездоровая технология». Но при этом, я не задумывался о том, а
+что же именно это для меня значит, и как определить что есть здоровая, а что
+нездоровая технология. Я классифицировал исключительно интуитивно, исходя из
+принципа «я так чувствую».
+
+Но раз есть классификация, то должны быть и критерии. Так? Так. И критерии я
+опираясь на собственное ощущение, вроде как, нашёл.
+
+<!--more-->
+
+# Критерии
+
+Не буду сильно уходить в сторону, для меня основные критерий «здоровости» — то,
+предполагает ли технология сохранение у пользователя контроля над ней. Даже
+можно сказать более витиевато — превращает ли технология пользователя в
+потребителя?
+
+Это был первый критерий. И он же главный. Из него вытекают уже такие критерии
+как потенциальная познаваемость, ремонтопригодность, и даже, внезапно,
+надёжность и долговечность.
+
+# Примеры
+
+Пройдёмся немного по примерам. Начнём с «нездоровых» технологий и конкретных
+примеров.
+
+## Трактора John Deere
+
+![Трактор John Deere](/posts/files/2025-12-21-img1.jpg)
+
+Это достаточно известная история, про то, как производитель напрямую влияет на
+то, как пользователь пользуется своей собственностью. А именно, запрещал
+самостоятельный ремонт тракторов своего производства своим клиентом, кроме как с
+помощью «сертифицированного» специалиста. Здесь буквально прослеживается
+критерий превращения пользователя из обладателя собственности в потребителя в
+некотором роде услуги «владения трактором». Когда ты вроде и заплатил за него
+полную стоимость, но владеешь им на пол шишечки, ведь ты не имеешь права
+распоряжаться им так, как ты хочешь (например, самостоятельно ремонтировать, раз
+у тебя подходящая квалификация). Притом, что фермеры, в большинстве своём, или
+достаточно квалифицированны или могли бы найти такого квалифицированного
+человека поблизости, а не ждать дни, пока до них доберётся специалист и починит.
+
+[Подробная история](https://habr.com/ru/companies/itelma/articles/477638/)
+
+## Машины для мороженного в McDonalds
+
+![та самая мороженница](/posts/files/2025-12-21-img2.jpg)
+
+Здесь примерно та же история, только чуть больше налёта монополии. Краткая суть
+в том, что владелец франшизы McDonalds принуждал своих франчайзи закупать
+исключительно конкретные машины конкретного производителя. Всё было бы не так
+плохо, если бы эти машины регулярно не ломались, а обслуживать их мог опять таки
+«сертифицированный» техник. Опять таки пользователь превращался в бесправного
+потребителя. Причём, можно было бы сказать, что это же хорошо, ведь пользователь
+только что-то сломает, но не сделает хорошо. Этот аргумент ломается об то, что
+как раз таки для этих машин предприимчивая пара людей начала производить
+устройство, которое делает этим машинам «jail-break» который затем позволял
+успешно чинить и эксплуатировать автоматы дальше. И ничего страшного не
+произошло! Во всяком случае, именно для пользователей. Производителю, конечно
+же, это не понравилось. И да, суды, давление монополиста и всё как мы любим.
+
+[Подробная история](https://habr.com/ru/articles/557746/)
+
+## BMW и некоторые другие автоконцерны
+
+![Подогрев по подписке](/posts/files/2025-12-21-img3.png)
+
+Здесь немного другая история. Думаю, все и так слышали про эту историю, которая
+бы могла быть шуткой, но оказалась реальностью. Я говорю про _подписку_ на, мать
+его, подогрев сидений. Ну и другие опции, я не вдавался подробно, не люблю эту
+марку автомобилей. Можете хоть бить, хоть резать, но такое мне не влезает в
+голову! Человек _уже_ купил автомобиль и всё что в нём находится. В том числе и
+нагревательные элементы в креслах! Почему он _должен_ покупать право на
+включение устройства которое он и так купил? Я этому не вижу ни одного
+оправдания. Мне плевать на хотелки жирных баварских подсвинков и я обеими руками
+поддерживаю хакеров, которые [джейл-брейкают](https://habr.com/ru/news/678362/)
+в автомобилях эти функции!
+
+Так же [недавно были
+новости](https://www.gazeta.ru/auto/2025/12/03/22117009.shtml?utm_auth=true) про
+то, что владельцы других немецких автомобилей в России остались по сути с
+дорогой грудой металла.
+
+## Принтеры и их зачипованные картриджи
+
+Эта проблема известна всем владельцам принтеров. 99% (источник статистики: мой
+потолок) современных принтеров не будут печатать неродным картриджем или
+картриджем, в который залиты чернила или засыпан тонер повторно (т.к. никому,
+кроме производителя, не нужный чип на картридже сказал что картридж уже Б/У).
+
+## Apple
+
+Мне нужно пояснять? Думаю, нет. Хорошо хоть что железо у них достойное и
+достаточно надёжное, насколько я могу судить. Это хоть как-то оправдывает их
+право на существование.
+
+## Проприетарный софт
+
+Тоже в пояснениях не нуждается. Хуже только прориетарный софт от амеров.
+
+## <что-то> по подписке
+
+Как бы не было это удобно, но подписочная модель, по определению ставит
+пользователя <del>раком</del> в позу бесправного потребителя. И не надо мне
+писать про удобство игр, фильмов, музыки, книг «по подписке». Нет, это никогда
+не будет хорошим выбором. Никогда. А впрочем, я не склонен осуждать тех кто этим
+пользуется, если человек отдаёт себе отчёт в том что с одной стороны он ничем не
+владеет, а с другой стороны он во власти капиталистических свиней, которые и
+деньги за подписку возьмут и личные данные куда надо продадут. Тут уж каждый сам
+решает, или смотреть условный нетфликс пока ему добрый дядя разрешает, или
+покупать тоже самое с чуть меньшим удобством на _зелённом нетфликсе_.
+
+# А что делать-то?
+
+На самом деле, решение то весьма простое: нужно более сознательно подходить к
+выбору как софта, так и железа. Нужно стараться отвечать себе на вопрос «А что я
+буду делать, если производитель или поставщик вдруг исчезнет или решит, что не
+хочет иметь со мной дел?». В общем случае нужно выбирать то, что ремонтопригодно
+(а в случае софта — свободно), имеет в свободном доступе исчерпывающие
+документации и руководства, а так же независимых поставщиков запасных деталей и
+сервисного обслуживания. Так же, в идеале, если нет возможности вообще не
+зависеть от вендора, то выбирать всегда локального, то есть российского или, на
+худой конец, из дружественных стран типа РБ или КНР. Конечно, это не защитит от
+рисков что вендор исчезнет и оставит нас без поддержки, но сильно их сократит.
+Но опять же, это если без вендора ну совсем никак. И самое худшее что можно
+сделать — довериться вендорам стран НАТО. В этом случае это хорошая заявка на
+премию Дарвина, не иначе.
+
+Нужны примеры? Их есть у меня, даже из личного опыта:
+
+- Мой МФУ купленный сто лет назад до сих пор служит мне верой и правдой, потому
+ что в своё время я озаботился тем, чтобы выбрать модель, которая выпущена
+ ровно до конкретной даты и поддерживает неоригинальные картриджи, которые
+ стоят три копейки и даже поддерживают самостоятельную засыпку тонера. А
+ ремонтопригодность у него такая, что я буквально могу починить его или сам,
+ или в ремонтной мастерской в моём доме.
+- Автомобиль. Мой автомобиль, конечно, технически сложное устройство и сам я в
+ нём мало что починю. Но он и не настолько технически сложный, чтобы его не
+ починили в произвольном, даже неавторизованном, сервисном центре. При этом, у
+ него нет никакой зависимости от «облаков» и он полностью автономен в этом
+ отношении и не зависит от воли производителя. А насколько мне известно, в
+ стране более чем достаточно запасных частей и узлов для него. Так что,
+ длительная эксплуатация не будет проблемой. Тем более уже более семи лет
+ автомобилю и за это время он показал себя только с лучшей стороны.
+- Все художественные книги у меня в формате fb2, который не подразумевает
+ поддержки DRM, а технические книги в формате PDF, который хоть и умеет вроде
+ как в DRM, но я с ним не сталкивался в своей библиотеке. И да, у меня именно
+ локальная библиотека продублированная на NAS и на резервный носитель.
+- Аналогично, музыка. Да, хоть и в большинстве своём в формате mp3, а не ogg
+ (просто руки не доходят конвертнуть), но она именно локальная. На ноуте,
+ телефоне и на флешке для прослушивания в автомобиле. И что в случае с
+ музыкой, что в случае книг (а так же и сериалов и прочего) — я _точно_ знаю
+ что само по себе, а точнее по воле какого-то «правообладателя», никуда не
+ денется от меня. Точнее, сохранность лежит целиком в моих руках, а не чьих-то
+ ещё.
+- Игры? Только те, что не подразумевают обязательной работы с интернетом, читай
+ «игр-сервисов». Это или старьё типа старых Fallout, или свободные игры типа
+ OpenTTD да Hedgewars. Так же у меня есть Nintendo Switch. Но у неё зарезан
+ интернет и играю исключительно с физических картриджей, которые никуда не
+ денутся. А чтобы не сломались — у меня есть [MigSwitch](https://migflash.ru/)
+ и дампер картриджей для него.
+- Ноутбук? Тут сложнее. Я постарался взять самый ремонтопригодный ноутбук из
+ доступных мне, с самым большим запасом прочности, чтобы служил мне не менее
+ десятилетия. Но тут без гарантий. Поэтому, если критерий долгосрочной
+ доступности сервиса для вас критичен, можно рассмотреть местных
+ производителей, которые хотя бы не исчезнут с рынка в результате геополитики и
+ не оставят нас без сервисного обслуживания. Например, ICL. Сам не проверял,
+ это только мысли.
+- E-mail. Самая здоровая технология для коммуникации. Во-первых,
+ децентрализованная, во-вторых, полностью открытая и свободная. В-третьих,
+ асинхронная и дающая мне возможность читать и отвечать на неё когда именно мне
+ удобно. А я такое очень ценю. Да, в современном мире чаты и синхронное общение
+ неизбежны, но я не делаю на них большую ставку и если, а точнее, когда
+ очередной мессенджер помрёт или будет заблокирован, для меня это не будет
+ трагедией, потому что я и не жду от них что они будут со мной всегда. Ну,
+ окей, jabber и irc — это тоже здоровые технологии, только ими почти никто не
+ пользуется. А для голосового общения есть свободные SIP да Mumble. Но ими тоже
+ пользуется полтора калеки. Вообще, про коммуникации, наверное, стоит будет
+ написать подробнее потом, тема очень обширная.
+- Всегда, когда возможно, выбираю именно лицензированное под GPL. Не MIT или
+ Apache. А именно GPL. Но здесь, скорее вкусовщина.
+
+Примеров здоровых технологий меньше и они меньше описаны, но это сознательно. Я
+собираюсь в дальнейшем, отдельными постами писать именно преимущественно о них,
+а тему «нездоровых» касаться сильно меньше. Поэтому нездоровые расписал сразу,
+чтобы больше особо не возвращаться.
+
+# Вместо заключения
+
+Надеюсь, я смог дать хотя бы примерное представление о том, что я считаю
+«здоровыми» и «нездоровыми» технологиями. Ожидаю ли я что люди когда-нибудь
+поумнеют и начнут использовать исключительно здоровые технологии? Да нет
+конечно. Здесь каждый сам кузнец собственного счастья.
+
+Я хотел ещё много что написать как примеры здоровых и нездоровых технологий, но
+тогда я бы пост бы не выпустил примерно никогда, так как тема бесконечная.
+
+Если есть желание, предлагаю обсудить со мной пост или по
+[e-mail](mailto:i@neonxp.ru) или там, где вы можете достичь меня. Позже в этом
+блоге, я всё же сделаю удобные комментарии, но это совсем другая история.
+
+А напоследок я хочу дать несколько ссылок в тему:
+
+- [Очень хороший набор примеров «вредных» и «менее вредных» вещей от Сергея
+ Матвеева](http://www.stargrave.org/Harmful.html). В принципе, это примерно те
+ же «нездоровые» и «здоровые» технологии. В целом, почти со всем я согласен, а
+ поэтому могу рекомендовать список как даже некоторое руководство по выбору
+ «здоровых» технологий.
+- [Пост на французском про «низкие
+ технологии»](https://ploum.net/2025-05-16-manifeste-lowtech.html) — тоже на
+ очень близкую тему.
+
+Если есть схожие по теме материалы — не стесняйтесь советовать, тема для меня
+интересная, с удовольствием ознакомлюсь. А сам её продолжу, возможно, уже на
+следующей неделе!
diff --git a/content/posts/2025-12-23-comments.md b/content/posts/2025-12-23-comments.md
new file mode 100644
index 0000000..9a82612
--- /dev/null
+++ b/content/posts/2025-12-23-comments.md
@@ -0,0 +1,12 @@
+---
+comments: true
+date: '2025-12-23T21:22:57+03:00'
+tags:
+- блог
+title: Появились комментарии в блоге
+---
+
+Наконец-то сделал комментарии в этом блоге. Да, максимально по гиковски. Просто
+через отправку e-mail на адрес специального бота. Потом, для удобства наверное
+придумаю и другим способом. А может и так оставлю, ведь e-mail самый лучший и
+самый универсальный способ связи, как ни крути.
diff --git a/content/posts/2025-12-24-email.md b/content/posts/2025-12-24-email.md
new file mode 100644
index 0000000..a4011d3
--- /dev/null
+++ b/content/posts/2025-12-24-email.md
@@ -0,0 +1,99 @@
+---
+comments: true
+date: '2025-12-24T21:00:22+03:00'
+tags:
+- разное
+- sicktech
+title: Почему я люблю e-mail?
+---
+
+Я действительно очень люблю старую-добрую электронную почту. И вот почему.
+
+<!--more-->
+
+Во-первых, как и всё, что делалось в раннее время Интернета — e-mail достаточно
+простая и открытая технология. А я очень ценю и то и то. В простой и открытой
+технологии _возможно_ разобраться, в отличие от закрытых проприетарных систем.
+
+Во-вторых, она действительно децентрализованная, а значит отвечает духу того,
+как проектировался Интернет ещё до того, как корпорации захватили его и
+подчинили своей монополии. Если оглянуться шире — многие ранние технологии
+интернета так же децентрализованные. Например, WWW, DNS, внезапно, IRC, XMPP...
+Да много примеров. Ещё мне приходит в голову GIT, который спроектирован так,
+что может работать поверх, практически, чего угодно! И да, в том числе, даже
+поверх просто e-mail!
+
+В-третьих, она достаточно универсальна. По факту, e-mail это просто контейнер,
+внутри которого можно положить практиески что угодно. Например, то что из себя
+обычно представляет письмо — это или обычный текстовый файл (plaintext) или HTML
+страничка (это более распространённые письма которые содержат богатую разметку).
+Так же в том же письме могут быть приложены используемые в нём картинки, или,
+например, файл содержащий приглашение на определённое событие в формате iCal.
+И в последнем случае, наверняка, почтовая программа корректно обработает этот
+файл и добавит событие в календарь. А так же, если пользователь решит принять
+или отклонить приглашение, ответ уйдёт так же по e-mail. Что интересно, в какой
+бы корпорации я не работал, именно так и работает система событий и календари.
+
+В-четвёртых, и это для меня самое главное. Почта, в отличие от всяких разных
+мессенджеров, не предполагает синхронного общения! А это значит что? Что у меня
+есть прорва времени чтобы _неторопясь_ в комфортном для меня режиме собраться с
+мыслями, неторопясь аккуратно написать письмо. Проверить. Перепроверить. Ещё
+подумать. И только после этого направить адресату. И выбросить из головы.
+
+Я человек в принципе неторопливый, и это меня устраивает гораздо больше, чем
+незримое эмоциональное давление от осознания того, что мой собеседник _видит_ в
+мессенджере что я уже прочитал его сообщение и ждёт ответа. А тянуть при этом и
+задерживать человека очень не хочется. Это НЕ комфортно.
+
+Мне такое неторопливое общение кажется очень и очень уютным. По этой же причине,
+мне так сильно импонирует вести обычный классический текстовый блог, когда более
+модно вести условный телеграм канал. Канал то у меня тоже есть, но он или для
+уведомлений о новых записях блога, или для совсем уж быстрых коротких заметок.
+
+Конечно, я не сумасшедший, и понимаю что есть множество вопросов, которые
+действительно требуют и быстрого ответа и синхронного общения. Конечно же, в
+таком случае использовать почту только потому что это почта — глупо и
+нерационально. Здесь я не буду спорить. Всё так.
+
+Но общение — это гораздо более широкое понятие, и оно может быть _разным_. И
+если есть возможность — я предпочту комфортное общение без психолгоического и
+эмоционального давления.
+
+# Немного про другие коммуникации
+
+И да, в контексте, [предпредыдущего поста](/posts/2025-12-21-sicktech/), e-mail
+— это однозначно «здоровая технология». Но не единственная. Раз уж немного
+отклонился в сторону, приведу из этой же области ещё немного «здоровых» из
+области коммуникаций:
+
+- [IRC](https://ru.ruwiki.ru/wiki/IRC) — радикальная простота протокола,
+ универсальность и распределённость. А в последнее время даже пытается ожить с
+ новой версией [IRCv3](https://ircv3.net/).
+- [Jabber](https://ru.ruwiki.ru/wiki/XMPP) — уже не так просто, зато так же
+ открыто и децентрализованно. Так же, в последние годы обрёл второе дыхание и
+ развивается.
+- [Mumble](https://ru.ruwiki.ru/wiki/Mumble) — свободный голосовой чат. Имеет
+ отличное качество звука при очень низкой задержке и потрелении трафика.
+
+Это далеко не исчерпывающий список, но он по факту покрывает основные
+потребности в коммуникации.
+
+## И снова про e-mail и внезапная экономия там, где её не ждёшь
+
+Возращаясь к e-mail, я написал этот пост не как призыв всем всё бросить и писать
+только письма, а только как напоминание, что такой инструмент есть (и есть по
+факту почти у всех!) и нужно не забывать о нём и использовать тогда, когда он
+уместен.
+
+Шутка про то, что «Эту часовую встречу можно было заменить просто одним емейлом»
+— далеко не шутка, и очень часто так и есть. Таким образом, в некотором роде,
+даже учитывая общий неторопливый стиль переписки — она, внезапно, может и помочь
+сэкономить время! Не самый очевидный вывод, но так и есть.
+
+И да, постарайтесь не загаживать свой почтовый ящик ненужными автоматическими
+сообщениями. Если не охото удалять то, что потенциально может пригодиться через
+N лет — просто отправьте в архив. Разгребать свой почтовый ящик и, если нужно,
+отвечать на письма — достаточно приятная рутина, которая отнимает не так уж
+много времени. И да, весьма уютная и медитативная рутина, а результат, когда
+непрочитанных писем нет, и письма аккуратно разложены по папкам или удалены —
+приносит ощущение хорошо сделанного полезного дела. Попробуйте ;)
diff --git a/content/posts/2025-12-27-osm.md b/content/posts/2025-12-27-osm.md
new file mode 100644
index 0000000..1c9881c
--- /dev/null
+++ b/content/posts/2025-12-27-osm.md
@@ -0,0 +1,40 @@
+---
+comments: true
+cover: /posts/files/2025-12-27-osm_img/photo.jpg
+date: '2025-12-27T19:42:04+03:00'
+tags:
+- прогулки
+title: Сходили на ярмарку OSM
+---
+
+Сегодня с супругой съездили для интереса на ярмарку
+[OSM](https://kzngo.ru/event/market-osm-14307) (нет, это не OpenStreetMaps, а
+OpenSpaceMarket :) ) в гастрокомплексе «Кайт». Ярмарка не скажу что была сильно
+большой, но нам понравилось. Купили всякого не сильно много, поднос да пару
+ароматных свечек с зимними ароматами. Фотографии покупок да и самой прогулки
+прилагаю. Всё таки у нас очень красивый город!
+
+P.S. на фотку можно кликнуть, тогда она откроется в большем размере.
+
+<!--more-->
+
+## Покупочки
+
+[![Покупки](/posts/files/2025-12-27-osm_img/thumbs/photo.jpg)](/posts/files/2025-12-27-osm_img/photo.jpg)
+
+## Прогулка
+
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_1.jpg)](/posts/files/2025-12-27-osm_img/photo_1.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_2.jpg)](/posts/files/2025-12-27-osm_img/photo_2.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_3.jpg)](/posts/files/2025-12-27-osm_img/photo_3.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_4.jpg)](/posts/files/2025-12-27-osm_img/photo_4.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_5.jpg)](/posts/files/2025-12-27-osm_img/photo_5.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_6.jpg)](/posts/files/2025-12-27-osm_img/photo_6.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_7.jpg)](/posts/files/2025-12-27-osm_img/photo_7.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_8.jpg)](/posts/files/2025-12-27-osm_img/photo_8.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_9.jpg)](/posts/files/2025-12-27-osm_img/photo_9.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_10.jpg)](/posts/files/2025-12-27-osm_img/photo_10.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_11.jpg)](/posts/files/2025-12-27-osm_img/photo_11.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_12.jpg)](/posts/files/2025-12-27-osm_img/photo_12.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_13.jpg)](/posts/files/2025-12-27-osm_img/photo_13.jpg)
+[![Прогулка](/posts/files/2025-12-27-osm_img/thumbs/photo_14.jpg)](/posts/files/2025-12-27-osm_img/photo_14.jpg)
diff --git a/content/posts/2025-12-28-philharmonic-park.md b/content/posts/2025-12-28-philharmonic-park.md
new file mode 100644
index 0000000..c692ecc
--- /dev/null
+++ b/content/posts/2025-12-28-philharmonic-park.md
@@ -0,0 +1,32 @@
+---
+comments: true
+cover: /posts/files/2025-12-28-philharmonic-park_img/photo_1_2025-12-28_21-35-40.jpg
+date: '2025-12-28T21:37:44+03:00'
+tags:
+- прогулки
+title: Прогулка в сквере филармонии
+---
+
+Вчера [гуляли с супругой на
+набережной](https://neonxp.ru/posts/2025-12-27-osm/), а сегодня чисто случайно
+решили прогуляться в сквере филармонии им.Тукая. Людей было немного, что только
+создавало больше уюта и ламповости этому хорошо украшенному скверику. К
+сожалению, гулять прям долго-долго особо не вышло, т.к. хоть и всего -7°, но
+продрогли сильно, т.к. гулять изначально не планировали и не были одеты
+соответственно. Сейчас дописываю этот пост и пытаюсь отогреться :)
+
+Как водится, дальше будет немного фотографий :)
+
+<!--more-->
+
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_2_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_2_2025-12-28_21-35-40.png)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_3_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_3_2025-12-28_21-35-40.png)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_5_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_5_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_9_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_9_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_19_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_19_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_20_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_20_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_25_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_25_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_27_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_27_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_30_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_30_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_31_2025-12-28_21-35-40.jpg)
+[![Прогулка](/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_32_2025-12-28_21-35-40.jpg)](/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.png)
diff --git a/content/posts/2025-12-29-newyear-excel.md b/content/posts/2025-12-29-newyear-excel.md
new file mode 100644
index 0000000..76916b2
--- /dev/null
+++ b/content/posts/2025-12-29-newyear-excel.md
@@ -0,0 +1,22 @@
+---
+comments: true
+cover: /posts/files/2025-12-29-newyear-excel_img/cover1.png
+date: '2025-12-29T11:47:14+03:00'
+tags:
+- разное
+title: Новогодний Excel
+---
+
+По мотивам одного недавнего поста с Пикабу, который мне уже лень искать. Немного
+предновогоднего офискора вам. Открываем в Excel или в LibreOffice
+соответствующий файл и жмём несколько раз клавишу F9.
+
+Делал я именно в LibreOffice, поэтому только там я точно уверен что работает как задуманно.
+
+- [Версия для MS Excel](/posts/files/2025-12-29-newyear-excel_img/new_year.xlsx)
+- [Версия для LibreOffice
+ Calc](/posts/files/2025-12-29-newyear-excel_img/new_year.ods)
+- [Онлайн версия в Документах
+ Mail.Ru](https://cloud.mail.ru/public/SF1M/ob3EeqtNF)
+
+![Результат](/posts/files/2025-12-29-newyear-excel_img/cover.png)
diff --git a/content/posts/2025-12-31-new-year.md b/content/posts/2025-12-31-new-year.md
new file mode 100644
index 0000000..7a3c3b0
--- /dev/null
+++ b/content/posts/2025-12-31-new-year.md
@@ -0,0 +1,28 @@
+---
+comments: true
+cover: /posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpg
+date: '2025-12-31T15:27:17+03:00'
+tags:
+- разное
+title: С Новым Годом!
+---
+
+С наступающим Новым Годом!
+
+![2026](/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.png)
+
+Предыдущий год был весьма и весьма разным. Было и хорошее и плохое. Отчёт по
+году как в [прошлом году](/posts/2024-12-31-new-year/) делать не буду. Честно
+говоря, нет особо моральных сил на это.
+
+<!--more-->
+
+Уходящий год для меня стал годом чередования работы и семейных забот. В целом,
+всё хорошо, но хотелось бы, чтобы следующий год был всё же проще, чего и всем
+желаю!
+
+А так желаю всем сил, крепкого здоровья и больше удачи! Желаю чтобы беды
+обходили ваш дом стороной.
+
+А пока, отправляюсь резать салатики и заниматься прочими домашними заботами, не
+всё же у компьютера сидеть :)
diff --git a/content/posts/2025-12-31-qchat.md b/content/posts/2025-12-31-qchat.md
new file mode 100644
index 0000000..8344933
--- /dev/null
+++ b/content/posts/2025-12-31-qchat.md
@@ -0,0 +1,46 @@
+---
+comments: true
+date: '2025-12-31T14:55:45+03:00'
+tags:
+- гиковское
+- IT
+title: qChat — чатик поверх SSH
+---
+
+Для собственного интереса написал на Go реализацию IRC-подобного чата, который
+работает поверх прокола SSH.
+
+Умеет пока немного, но много я от него и не хотел. Основная идея в том, чтобы
+его можно было запустить без конфигурации, с минимальными усилиями на любой
+«картошке» и чтобы он могу обслуживать, например, небольшую группу пользователей
+в одной локальной сети (хотя может работать и по интернету).
+
+Получилось очень гиковски и лампово. Так сказать, чатик на случай ядерной войны
+:)
+
+<!--more-->
+
+Приглашаю забежать на огонёк, если умеете пользоваться SSH:
+
+```
+ssh neonxp.ru -p 1337
+```
+
+Продублирую из README описание основных команд:
+
+- `/help` - эта справка.
+- `/join [chan]` - подключиться к каналу [chan]. Если его нет, он будет создан.
+- `/chans` - список каналов.
+- `/users` - список пользователей на сервере (не на канале, а именно на
+ сервере).
+- `/me [message]` - отправка сообщения как бы от третьего лица.
+
+Форматирование сообщений:
+
+- `*Полужирный*`
+- `+Курсив+`
+- `-Зачёркнутый текст-`
+- `_Подчёркнутый текст_`
+
+Про техническое описание — есть отдельная страница:
+[/projects/qchat/](/projects/qchat/)
diff --git a/content/posts/_index.md b/content/posts/_index.md
new file mode 100644
index 0000000..0fbcdff
--- /dev/null
+++ b/content/posts/_index.md
@@ -0,0 +1,8 @@
+---
+title: Блог
+cascade:
+ params:
+ comments: true
+---
+
+Просто мой блог
diff --git a/content/posts/files/2021-02-13-jsonnet_logo.webp b/content/posts/files/2021-02-13-jsonnet_logo.webp
new file mode 100644
index 0000000..45c63a6
--- /dev/null
+++ b/content/posts/files/2021-02-13-jsonnet_logo.webp
Binary files differ
diff --git a/content/posts/files/2024-07-13-joplin_joplin.webp b/content/posts/files/2024-07-13-joplin_joplin.webp
new file mode 100644
index 0000000..33326b7
--- /dev/null
+++ b/content/posts/files/2024-07-13-joplin_joplin.webp
Binary files differ
diff --git a/content/posts/files/2024-10-06-цитатник-рунета_bash_org.webp b/content/posts/files/2024-10-06-цитатник-рунета_bash_org.webp
new file mode 100644
index 0000000..e142633
--- /dev/null
+++ b/content/posts/files/2024-10-06-цитатник-рунета_bash_org.webp
Binary files differ
diff --git a/content/posts/files/2024-10-17-книги-2_Rama16wiki.webp b/content/posts/files/2024-10-17-книги-2_Rama16wiki.webp
new file mode 100644
index 0000000..b4d9ce9
--- /dev/null
+++ b/content/posts/files/2024-10-17-книги-2_Rama16wiki.webp
Binary files differ
diff --git a/content/posts/files/2024-11-17-obsidian_img/logo.webp b/content/posts/files/2024-11-17-obsidian_img/logo.webp
new file mode 100644
index 0000000..d5c747a
--- /dev/null
+++ b/content/posts/files/2024-11-17-obsidian_img/logo.webp
Binary files differ
diff --git a/content/posts/files/2024-11-17-obsidian_img/publish.webp b/content/posts/files/2024-11-17-obsidian_img/publish.webp
new file mode 100644
index 0000000..f8add88
--- /dev/null
+++ b/content/posts/files/2024-11-17-obsidian_img/publish.webp
Binary files differ
diff --git a/content/posts/files/2024-11-17-obsidian_img/templater.webp b/content/posts/files/2024-11-17-obsidian_img/templater.webp
new file mode 100644
index 0000000..facdd86
--- /dev/null
+++ b/content/posts/files/2024-11-17-obsidian_img/templater.webp
Binary files differ
diff --git a/content/posts/files/2024-11-27-hyperlocality_img/90e.webp b/content/posts/files/2024-11-27-hyperlocality_img/90e.webp
new file mode 100644
index 0000000..948d808
--- /dev/null
+++ b/content/posts/files/2024-11-27-hyperlocality_img/90e.webp
Binary files differ
diff --git a/content/posts/files/2024-11-27-hyperlocality_img/braindance.webp b/content/posts/files/2024-11-27-hyperlocality_img/braindance.webp
new file mode 100644
index 0000000..965e145
--- /dev/null
+++ b/content/posts/files/2024-11-27-hyperlocality_img/braindance.webp
Binary files differ
diff --git a/content/posts/files/2024-11-27-hyperlocality_img/camp.webp b/content/posts/files/2024-11-27-hyperlocality_img/camp.webp
new file mode 100644
index 0000000..a07d8ed
--- /dev/null
+++ b/content/posts/files/2024-11-27-hyperlocality_img/camp.webp
Binary files differ
diff --git a/content/posts/files/2024-11-27-hyperlocality_img/in-internet.webp b/content/posts/files/2024-11-27-hyperlocality_img/in-internet.webp
new file mode 100644
index 0000000..56d8c50
--- /dev/null
+++ b/content/posts/files/2024-11-27-hyperlocality_img/in-internet.webp
Binary files differ
diff --git a/content/posts/files/2024-11-29-hobbies_dozor.webp b/content/posts/files/2024-11-29-hobbies_dozor.webp
new file mode 100644
index 0000000..eab9913
--- /dev/null
+++ b/content/posts/files/2024-11-29-hobbies_dozor.webp
Binary files differ
diff --git a/content/posts/files/2024-12-12-guessr_logo.webp b/content/posts/files/2024-12-12-guessr_logo.webp
new file mode 100644
index 0000000..3a414cd
--- /dev/null
+++ b/content/posts/files/2024-12-12-guessr_logo.webp
Binary files differ
diff --git a/content/posts/files/2024-12-15-conditional-operator-go_ternary.webp b/content/posts/files/2024-12-15-conditional-operator-go_ternary.webp
new file mode 100644
index 0000000..5eeea58
--- /dev/null
+++ b/content/posts/files/2024-12-15-conditional-operator-go_ternary.webp
Binary files differ
diff --git a/content/posts/files/2024-12-15-posse_posse.webp b/content/posts/files/2024-12-15-posse_posse.webp
new file mode 100644
index 0000000..aad6230
--- /dev/null
+++ b/content/posts/files/2024-12-15-posse_posse.webp
Binary files differ
diff --git a/content/posts/files/2024-12-17-infra_cover.webp b/content/posts/files/2024-12-17-infra_cover.webp
new file mode 100644
index 0000000..c02c8be
--- /dev/null
+++ b/content/posts/files/2024-12-17-infra_cover.webp
Binary files differ
diff --git a/content/posts/files/2024-12-30-irc_logo.webp b/content/posts/files/2024-12-30-irc_logo.webp
new file mode 100644
index 0000000..26c4182
--- /dev/null
+++ b/content/posts/files/2024-12-30-irc_logo.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/1.webp b/content/posts/files/2024-12-31-new-year_img/1.webp
new file mode 100644
index 0000000..f320b83
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/1.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/2.webp b/content/posts/files/2024-12-31-new-year_img/2.webp
new file mode 100644
index 0000000..49a1ed1
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/2.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/2025.webp b/content/posts/files/2024-12-31-new-year_img/2025.webp
new file mode 100644
index 0000000..9cc3c0b
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/2025.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/3.webp b/content/posts/files/2024-12-31-new-year_img/3.webp
new file mode 100644
index 0000000..64dedc8
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/3.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/4.webp b/content/posts/files/2024-12-31-new-year_img/4.webp
new file mode 100644
index 0000000..b5af0b5
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/4.webp
Binary files differ
diff --git a/content/posts/files/2024-12-31-new-year_img/5.webp b/content/posts/files/2024-12-31-new-year_img/5.webp
new file mode 100644
index 0000000..86a8c2d
--- /dev/null
+++ b/content/posts/files/2024-12-31-new-year_img/5.webp
Binary files differ
diff --git a/content/posts/files/2025-12-21-img1.jpg b/content/posts/files/2025-12-21-img1.jpg
new file mode 100644
index 0000000..2dd29f9
--- /dev/null
+++ b/content/posts/files/2025-12-21-img1.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-21-img2.jpg b/content/posts/files/2025-12-21-img2.jpg
new file mode 100644
index 0000000..0ea8467
--- /dev/null
+++ b/content/posts/files/2025-12-21-img2.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-21-img3.png b/content/posts/files/2025-12-21-img3.png
new file mode 100644
index 0000000..3e41caf
--- /dev/null
+++ b/content/posts/files/2025-12-21-img3.png
Binary files differ
diff --git a/content/posts/files/2025-12-21-sicktech.png b/content/posts/files/2025-12-21-sicktech.png
new file mode 100644
index 0000000..32c7b9f
--- /dev/null
+++ b/content/posts/files/2025-12-21-sicktech.png
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo.jpg b/content/posts/files/2025-12-27-osm_img/photo.jpg
new file mode 100644
index 0000000..4ea3f89
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_1.jpg b/content/posts/files/2025-12-27-osm_img/photo_1.jpg
new file mode 100644
index 0000000..d14ed56
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_1.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_10.jpg b/content/posts/files/2025-12-27-osm_img/photo_10.jpg
new file mode 100644
index 0000000..66a5727
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_10.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_11.jpg b/content/posts/files/2025-12-27-osm_img/photo_11.jpg
new file mode 100644
index 0000000..aed7c5b
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_11.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_12.jpg b/content/posts/files/2025-12-27-osm_img/photo_12.jpg
new file mode 100644
index 0000000..b78b33b
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_12.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_13.jpg b/content/posts/files/2025-12-27-osm_img/photo_13.jpg
new file mode 100644
index 0000000..6bb6bd0
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_13.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_14.jpg b/content/posts/files/2025-12-27-osm_img/photo_14.jpg
new file mode 100644
index 0000000..806471f
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_14.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_2.jpg b/content/posts/files/2025-12-27-osm_img/photo_2.jpg
new file mode 100644
index 0000000..d4f788a
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_2.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_3.jpg b/content/posts/files/2025-12-27-osm_img/photo_3.jpg
new file mode 100644
index 0000000..74ef585
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_3.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_4.jpg b/content/posts/files/2025-12-27-osm_img/photo_4.jpg
new file mode 100644
index 0000000..ea5a5d0
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_4.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_5.jpg b/content/posts/files/2025-12-27-osm_img/photo_5.jpg
new file mode 100644
index 0000000..462b570
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_5.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_6.jpg b/content/posts/files/2025-12-27-osm_img/photo_6.jpg
new file mode 100644
index 0000000..253af73
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_6.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_7.jpg b/content/posts/files/2025-12-27-osm_img/photo_7.jpg
new file mode 100644
index 0000000..cd92319
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_7.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_8.jpg b/content/posts/files/2025-12-27-osm_img/photo_8.jpg
new file mode 100644
index 0000000..bebc4b3
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_8.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/photo_9.jpg b/content/posts/files/2025-12-27-osm_img/photo_9.jpg
new file mode 100644
index 0000000..8e4358b
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/photo_9.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo.jpg
new file mode 100644
index 0000000..ae1a7eb
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_1.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_1.jpg
new file mode 100644
index 0000000..7d3df90
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_1.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_10.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_10.jpg
new file mode 100644
index 0000000..ee69188
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_10.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_11.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_11.jpg
new file mode 100644
index 0000000..72c288f
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_11.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_12.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_12.jpg
new file mode 100644
index 0000000..8bbd32c
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_12.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_13.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_13.jpg
new file mode 100644
index 0000000..1608d5b
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_13.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_14.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_14.jpg
new file mode 100644
index 0000000..5cab91a
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_14.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_2.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_2.jpg
new file mode 100644
index 0000000..870578b
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_2.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_3.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_3.jpg
new file mode 100644
index 0000000..44b2637
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_3.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_4.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_4.jpg
new file mode 100644
index 0000000..462b1bc
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_4.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_5.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_5.jpg
new file mode 100644
index 0000000..4c9d1d6
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_5.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_6.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_6.jpg
new file mode 100644
index 0000000..9ad1280
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_6.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_7.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_7.jpg
new file mode 100644
index 0000000..7054792
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_7.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_8.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_8.jpg
new file mode 100644
index 0000000..edd7f59
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_8.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-27-osm_img/thumbs/photo_9.jpg b/content/posts/files/2025-12-27-osm_img/thumbs/photo_9.jpg
new file mode 100644
index 0000000..859bade
--- /dev/null
+++ b/content/posts/files/2025-12-27-osm_img/thumbs/photo_9.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_19_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_19_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..a7602ed
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_19_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_1_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_1_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..d0ff6f2
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_1_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_20_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_20_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..7007409
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_20_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_25_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_25_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..17d8e06
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_25_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_27_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_27_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..87658fc
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_27_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_2_2025-12-28_21-35-40.png b/content/posts/files/2025-12-28-philharmonic-park_img/photo_2_2025-12-28_21-35-40.png
new file mode 100644
index 0000000..de29bfb
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_2_2025-12-28_21-35-40.png
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_30_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_30_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..e028728
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_30_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_31_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_31_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..0c83ba7
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_31_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.png b/content/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.png
new file mode 100644
index 0000000..00eddcc
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_32_2025-12-28_21-35-40.png
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_3_2025-12-28_21-35-40.png b/content/posts/files/2025-12-28-philharmonic-park_img/photo_3_2025-12-28_21-35-40.png
new file mode 100644
index 0000000..a01eae9
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_3_2025-12-28_21-35-40.png
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_5_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_5_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..615da4b
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_5_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/photo_9_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/photo_9_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..69ad012
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/photo_9_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_19_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_19_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..7768b6e
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_19_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_1_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_1_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..369d229
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_1_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_20_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_20_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..35e1cd5
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_20_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_25_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_25_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..ccb1005
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_25_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_27_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_27_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..066be5c
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_27_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_2_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_2_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..ccab7b5
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_2_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_30_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_30_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..71aebf0
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_30_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..f9ef2da
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_31_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_32_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_32_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..381faff
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_32_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_3_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_3_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..f9e8a43
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_3_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_5_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_5_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..302bb88
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_5_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_9_2025-12-28_21-35-40.jpg b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_9_2025-12-28_21-35-40.jpg
new file mode 100644
index 0000000..3c6b8f1
--- /dev/null
+++ b/content/posts/files/2025-12-28-philharmonic-park_img/thumbs/photo_9_2025-12-28_21-35-40.jpg
Binary files differ
diff --git a/content/posts/files/2025-12-29-newyear-excel_img/cover.png b/content/posts/files/2025-12-29-newyear-excel_img/cover.png
new file mode 100644
index 0000000..ae331d5
--- /dev/null
+++ b/content/posts/files/2025-12-29-newyear-excel_img/cover.png
Binary files differ
diff --git a/content/posts/files/2025-12-29-newyear-excel_img/cover1.png b/content/posts/files/2025-12-29-newyear-excel_img/cover1.png
new file mode 100644
index 0000000..b252f6e
--- /dev/null
+++ b/content/posts/files/2025-12-29-newyear-excel_img/cover1.png
Binary files differ
diff --git a/content/posts/files/2025-12-29-newyear-excel_img/new_year.ods b/content/posts/files/2025-12-29-newyear-excel_img/new_year.ods
new file mode 100644
index 0000000..2d8aad2
--- /dev/null
+++ b/content/posts/files/2025-12-29-newyear-excel_img/new_year.ods
Binary files differ
diff --git a/content/posts/files/2025-12-29-newyear-excel_img/new_year.xlsx b/content/posts/files/2025-12-29-newyear-excel_img/new_year.xlsx
new file mode 100644
index 0000000..ca4469c
--- /dev/null
+++ b/content/posts/files/2025-12-29-newyear-excel_img/new_year.xlsx
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/1.webp b/content/posts/files/2025-travel-1_img/1.webp
new file mode 100644
index 0000000..bc037f2
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/1.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/10.webp b/content/posts/files/2025-travel-1_img/10.webp
new file mode 100644
index 0000000..32bb46f
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/10.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/11.webp b/content/posts/files/2025-travel-1_img/11.webp
new file mode 100644
index 0000000..12f0933
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/11.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/12.webp b/content/posts/files/2025-travel-1_img/12.webp
new file mode 100644
index 0000000..c797bf3
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/12.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/13.webp b/content/posts/files/2025-travel-1_img/13.webp
new file mode 100644
index 0000000..96db2e5
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/13.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/14.webp b/content/posts/files/2025-travel-1_img/14.webp
new file mode 100644
index 0000000..66a53d8
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/14.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/15.webp b/content/posts/files/2025-travel-1_img/15.webp
new file mode 100644
index 0000000..a71672b
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/15.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/16.webp b/content/posts/files/2025-travel-1_img/16.webp
new file mode 100644
index 0000000..e690ba3
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/16.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/17.webp b/content/posts/files/2025-travel-1_img/17.webp
new file mode 100644
index 0000000..81fa05b
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/17.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/18.webp b/content/posts/files/2025-travel-1_img/18.webp
new file mode 100644
index 0000000..5905999
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/18.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/19.webp b/content/posts/files/2025-travel-1_img/19.webp
new file mode 100644
index 0000000..ab80bd9
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/19.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/2.webp b/content/posts/files/2025-travel-1_img/2.webp
new file mode 100644
index 0000000..b9ecc86
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/2.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/20.webp b/content/posts/files/2025-travel-1_img/20.webp
new file mode 100644
index 0000000..1cc4e35
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/20.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/21.webp b/content/posts/files/2025-travel-1_img/21.webp
new file mode 100644
index 0000000..0f3cc2c
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/21.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/22.webp b/content/posts/files/2025-travel-1_img/22.webp
new file mode 100644
index 0000000..b2d032d
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/22.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/23.webp b/content/posts/files/2025-travel-1_img/23.webp
new file mode 100644
index 0000000..00ad3f9
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/23.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/3.webp b/content/posts/files/2025-travel-1_img/3.webp
new file mode 100644
index 0000000..37ec362
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/3.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/4.webp b/content/posts/files/2025-travel-1_img/4.webp
new file mode 100644
index 0000000..dfb29bd
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/4.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/5.webp b/content/posts/files/2025-travel-1_img/5.webp
new file mode 100644
index 0000000..b5e1a8f
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/5.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/6.webp b/content/posts/files/2025-travel-1_img/6.webp
new file mode 100644
index 0000000..6b6bbca
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/6.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/7.webp b/content/posts/files/2025-travel-1_img/7.webp
new file mode 100644
index 0000000..fcce61f
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/7.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/8.webp b/content/posts/files/2025-travel-1_img/8.webp
new file mode 100644
index 0000000..b777d4c
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/8.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/9.webp b/content/posts/files/2025-travel-1_img/9.webp
new file mode 100644
index 0000000..f40de11
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/9.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_1.webp b/content/posts/files/2025-travel-1_img/preview_1.webp
new file mode 100644
index 0000000..c18dc0c
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_1.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_10.webp b/content/posts/files/2025-travel-1_img/preview_10.webp
new file mode 100644
index 0000000..4d23293
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_10.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_11.webp b/content/posts/files/2025-travel-1_img/preview_11.webp
new file mode 100644
index 0000000..07e38b6
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_11.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_12.webp b/content/posts/files/2025-travel-1_img/preview_12.webp
new file mode 100644
index 0000000..7a23c41
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_12.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_13.webp b/content/posts/files/2025-travel-1_img/preview_13.webp
new file mode 100644
index 0000000..953fac9
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_13.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_14.webp b/content/posts/files/2025-travel-1_img/preview_14.webp
new file mode 100644
index 0000000..08cb032
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_14.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_15.webp b/content/posts/files/2025-travel-1_img/preview_15.webp
new file mode 100644
index 0000000..627702d
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_15.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_16.webp b/content/posts/files/2025-travel-1_img/preview_16.webp
new file mode 100644
index 0000000..2bfa651
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_16.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_17.webp b/content/posts/files/2025-travel-1_img/preview_17.webp
new file mode 100644
index 0000000..814ace2
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_17.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_18.webp b/content/posts/files/2025-travel-1_img/preview_18.webp
new file mode 100644
index 0000000..82422a8
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_18.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_19.webp b/content/posts/files/2025-travel-1_img/preview_19.webp
new file mode 100644
index 0000000..b234b0c
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_19.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_2.webp b/content/posts/files/2025-travel-1_img/preview_2.webp
new file mode 100644
index 0000000..c730b03
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_2.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_20.webp b/content/posts/files/2025-travel-1_img/preview_20.webp
new file mode 100644
index 0000000..266a83e
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_20.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_21.webp b/content/posts/files/2025-travel-1_img/preview_21.webp
new file mode 100644
index 0000000..3cab02c
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_21.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_22.webp b/content/posts/files/2025-travel-1_img/preview_22.webp
new file mode 100644
index 0000000..5530efa
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_22.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_23.webp b/content/posts/files/2025-travel-1_img/preview_23.webp
new file mode 100644
index 0000000..aca8d32
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_23.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_3.webp b/content/posts/files/2025-travel-1_img/preview_3.webp
new file mode 100644
index 0000000..511e4c4
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_3.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_4.webp b/content/posts/files/2025-travel-1_img/preview_4.webp
new file mode 100644
index 0000000..bfbe20e
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_4.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_5.webp b/content/posts/files/2025-travel-1_img/preview_5.webp
new file mode 100644
index 0000000..5d5811d
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_5.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_6.webp b/content/posts/files/2025-travel-1_img/preview_6.webp
new file mode 100644
index 0000000..076e35b
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_6.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_7.webp b/content/posts/files/2025-travel-1_img/preview_7.webp
new file mode 100644
index 0000000..02cd89a
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_7.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_8.webp b/content/posts/files/2025-travel-1_img/preview_8.webp
new file mode 100644
index 0000000..4c87281
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_8.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/preview_9.webp b/content/posts/files/2025-travel-1_img/preview_9.webp
new file mode 100644
index 0000000..3791fbb
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/preview_9.webp
Binary files differ
diff --git a/content/posts/files/2025-travel-1_img/saologo.png b/content/posts/files/2025-travel-1_img/saologo.png
new file mode 100644
index 0000000..3fceeeb
--- /dev/null
+++ b/content/posts/files/2025-travel-1_img/saologo.png
Binary files differ
diff --git a/content/posts/files/hype_curve.png b/content/posts/files/hype_curve.png
new file mode 100644
index 0000000..9147d5e
--- /dev/null
+++ b/content/posts/files/hype_curve.png
Binary files differ
diff --git a/content/posts/files/laughing-man.jpeg b/content/posts/files/laughing-man.jpeg
new file mode 100644
index 0000000..652603c
--- /dev/null
+++ b/content/posts/files/laughing-man.jpeg
Binary files differ
diff --git a/content/posts/files/lets-code-3d_img/1.jpg b/content/posts/files/lets-code-3d_img/1.jpg
new file mode 100644
index 0000000..87f41e7
--- /dev/null
+++ b/content/posts/files/lets-code-3d_img/1.jpg
Binary files differ
diff --git a/content/posts/files/lets-code-3d_img/2.jpg b/content/posts/files/lets-code-3d_img/2.jpg
new file mode 100644
index 0000000..db14947
--- /dev/null
+++ b/content/posts/files/lets-code-3d_img/2.jpg
Binary files differ
diff --git a/content/posts/files/lets-code-3d_img/3.png b/content/posts/files/lets-code-3d_img/3.png
new file mode 100644
index 0000000..9887043
--- /dev/null
+++ b/content/posts/files/lets-code-3d_img/3.png
Binary files differ
diff --git a/content/posts/files/lets-code-3d_img/4.jpg b/content/posts/files/lets-code-3d_img/4.jpg
new file mode 100644
index 0000000..4e7ebc7
--- /dev/null
+++ b/content/posts/files/lets-code-3d_img/4.jpg
Binary files differ
diff --git a/content/posts/files/lets-code-3d_result.stl b/content/posts/files/lets-code-3d_result.stl
new file mode 100644
index 0000000..fdd4d19
--- /dev/null
+++ b/content/posts/files/lets-code-3d_result.stl
Binary files differ
diff --git a/content/posts/files/lets-code-3d_source.scad b/content/posts/files/lets-code-3d_source.scad
new file mode 100644
index 0000000..b651cb3
--- /dev/null
+++ b/content/posts/files/lets-code-3d_source.scad
@@ -0,0 +1,66 @@
+// Толщина стенки
+wall = 2;
+
+// Высота внешняя
+height = 12;
+
+// Длина основной части (внутренняя)
+l1 = 15;
+
+// Длина носика (внутренняя)
+l2 = 20;
+
+// Ширина у основания (внутренняя)
+w = 15;
+
+// Толщина метала бокорезов
+toolWidth = 2.1;
+
+// Нижняя крышка
+cover(0);
+
+// Верхняя крышка
+cover(height - wall);
+
+// Корпус
+difference() {
+ linear_extrude(height)
+ polygon(
+ [ // Полигон идёт против часовой стрелки
+ [0, 0],
+ [wall, 0],
+ [wall, l1],
+ [w / 2 + wall, l1 + l2], // Внутренний кончик носика
+ [w + wall, l1],
+ [w + wall, 0],
+ [w + wall * 2, 0],
+ [w + wall * 2, l1],
+ [w / 2 + wall / 2 + wall, l1 + l2 + wall], // Внешний кончик носика
+ [w / 2 - wall / 2 + wall, l1 + l2 + wall],
+ [0, l1],
+ ]
+ );
+
+ // Вырезы
+ translate([w + wall, 0, height / 2 - toolWidth]) // Правый вырез чуть ниже середины
+ cube([wall, l1 / 2, toolWidth]);
+ translate([0, 0, height / 2]) // Левый вырез чуть выше середины
+ cube([wall, l1 / 2, toolWidth]);
+}
+
+
+// Крышка
+module cover(z) {
+ translate([0, 0, z])
+ linear_extrude(wall)
+ polygon(
+ [ // Полигон идёт против часовой стрелки
+ [0, 0],
+ [w + wall * 2, 0],
+ [w + wall * 2, l1],
+ [w / 2 + wall / 2 + wall, l1 + l2 + wall],
+ [w / 2 - wall / 2 + wall, l1 + l2 + wall],
+ [0, l1],
+ ]
+ );
+}
diff --git a/content/posts/files/makeup-organizer_img/1.png b/content/posts/files/makeup-organizer_img/1.png
new file mode 100644
index 0000000..8f26704
--- /dev/null
+++ b/content/posts/files/makeup-organizer_img/1.png
Binary files differ
diff --git a/content/posts/files/makeup-organizer_img/2.png b/content/posts/files/makeup-organizer_img/2.png
new file mode 100644
index 0000000..6613744
--- /dev/null
+++ b/content/posts/files/makeup-organizer_img/2.png
Binary files differ
diff --git a/content/posts/files/makeup-organizer_img/3.png b/content/posts/files/makeup-organizer_img/3.png
new file mode 100644
index 0000000..993135e
--- /dev/null
+++ b/content/posts/files/makeup-organizer_img/3.png
Binary files differ
diff --git a/content/posts/files/makeup-organizer_organizer.tar.zst b/content/posts/files/makeup-organizer_organizer.tar.zst
new file mode 100644
index 0000000..7e2e2bb
--- /dev/null
+++ b/content/posts/files/makeup-organizer_organizer.tar.zst
Binary files differ
diff --git a/content/posts/files/meshtastic_img/tbeam.jpg b/content/posts/files/meshtastic_img/tbeam.jpg
new file mode 100644
index 0000000..214dc21
--- /dev/null
+++ b/content/posts/files/meshtastic_img/tbeam.jpg
Binary files differ
diff --git a/content/posts/files/meshtastic_img/tbeam.webp b/content/posts/files/meshtastic_img/tbeam.webp
new file mode 100644
index 0000000..7e89d94
--- /dev/null
+++ b/content/posts/files/meshtastic_img/tbeam.webp
Binary files differ
diff --git a/content/projects/_index.md b/content/projects/_index.md
new file mode 100644
index 0000000..71e1ed3
--- /dev/null
+++ b/content/projects/_index.md
@@ -0,0 +1,9 @@
+---
+order: "40"
+title: Проекты
+---
+
+Разные мои проекты
+
+Ещё больше — на <a href="https://gitrepo.ru/NeonXP">git репозитории</a>
+и на <a href="https://go.neonxp.ru/">моих go пакетах</a>.
diff --git a/content/projects/games/bubblebreaker.p8.png b/content/projects/games/bubblebreaker.p8.png
new file mode 100644
index 0000000..9bcf747
--- /dev/null
+++ b/content/projects/games/bubblebreaker.p8.png
Binary files differ
diff --git a/content/projects/games/gameof15.p8.png b/content/projects/games/gameof15.p8.png
new file mode 100644
index 0000000..5c9dfa0
--- /dev/null
+++ b/content/projects/games/gameof15.p8.png
Binary files differ
diff --git a/content/projects/games/index.md b/content/projects/games/index.md
new file mode 100644
index 0000000..4533a08
--- /dev/null
+++ b/content/projects/games/index.md
@@ -0,0 +1,30 @@
+---
+title: PICO-8
+---
+
+Мои небольшие игрушки на прекрасном движке PICO-8
+
+[![/projects/games/bubblebreaker.p8.png](/projects/games/bubblebreaker.p8.png)](/projects/games/bubblebreaker.p8.png)
+[![/projects/games/gameof15.p8.png](/projects/games/gameof15.p8.png)](/projects/games/gameof15.p8.png)
+[![/projects/games/lines.p8.png](/projects/games/lines.p8.png)](/projects/games/lines.p8.png)
+[![/projects/games/snake.p8.png](/projects/games/snake.p8.png)](/projects/games/snake.p8.png)
+
+И на всякий случай дистрибутивы самих приложений. Ведь их больше не купить у нас в стране, а значит это не пиратство, а корсарство! Яррр!
+
+# PICO-8
+
+| Linux | macOS | Windows |
+|-------|-------|---------|
+|[Скачать](/files/pico-8_0_2_6b_amd64.zip)|[Скачать](/files/pico-8_0_2_6b_osx.zip)|[Скачать](/files/pico-8_0_2_6b_windows.zip)|
+
+# Picotron
+
+| Linux | macOS | Windows |
+|-------|-------|---------|
+|[Скачать](/files/picotron_0_1_0g_amd64.zip)|[Скачать](/files/picotron_0_1_0g_osx.zip)|[Скачать](/files/picotron_0_1_0g_windows.zip)|
+
+# Voxatron
+
+| Linux | macOS | Windows |
+|-------|-------|---------|
+|[Скачать](/files/voxatron_0_3_5b_amd64.zip)|[Скачать](/files/voxatron_0_3_5b_osx.zip)|[Скачать](/files/voxatron_0_3_5b_windows.zip)|
diff --git a/content/projects/games/lines.p8.png b/content/projects/games/lines.p8.png
new file mode 100644
index 0000000..aa5ea39
--- /dev/null
+++ b/content/projects/games/lines.p8.png
Binary files differ
diff --git a/content/projects/games/snake.p8.png b/content/projects/games/snake.p8.png
new file mode 100644
index 0000000..14831dc
--- /dev/null
+++ b/content/projects/games/snake.p8.png
Binary files differ
diff --git a/content/projects/qchat.md b/content/projects/qchat.md
new file mode 100644
index 0000000..ed3b9a0
--- /dev/null
+++ b/content/projects/qchat.md
@@ -0,0 +1,89 @@
+---
+title: 'qChat - quick chat'
+---
+
+Репозиторий: https://gitrepo.ru/NeonXP/qChat
+
+Очень маленький и минималистичный чат, который реализует собой чат поверх SSH.
+
+Внешних зависимостей нет, должен работать на любой картошке.
+
+Подключение к демонстрационному чату:
+
+```
+ssh neonxp.ru -p 1337
+```
+
+## Установка и запуск
+
+Просто скачайте и запустите бинарник для соответствующей платформы. При первом
+запуске в текущей рабочей директории будет создан конфиг файл с умолчальной
+конфигурацией. При последующих запусках — будет он использоваться и не
+пересоздаваться.
+
+В конфиге лежит приватный ключ! Его нужно хранить в секрете. Остальные параметры
+там — дефолтный список каналов и название сервера. Их можно менять.
+
+### Ссылки для скачивания
+
+#### v0.0.2 [Исходники](https://gitrepo.ru/NeonXP/qChat/archive/v0.0.2.tar.gz)
+
+Готовые бинарники:
+
+- [Linux amd64](/files/qchat/v0.0.2/qchat-linux-amd64.tar.gz)
+- [Linux arm64](/files/qchat/v0.0.2/qchat-linux-arm64.tar.gz)
+- [Linux x86](/files/qchat/v0.0.2/qchat-linux-386.tar.gz)
+- [Linux arm/v6](/files/qchat/v0.0.2/qchat-linux-arm-v6.tar.gz)
+- [Linux arm/v7](/files/qchat/v0.0.2/qchat-linux-arm-v7.tar.gz)
+- [macOS amd64](/files/qchat/v0.0.2/qchat-darwin-amd64.tar.gz)
+- [macOS arm64](/files/qchat/v0.0.2/qchat-darwin-arm64.tar.gz)
+- [Windows x32](/files/qchat/v0.0.2/qchat-windows-386.zip)
+- [Windows x64](/files/qchat/v0.0.2/qchat-windows-amd64.zip)
+- [dragonfly amd64](/files/qchat/v0.0.2/qchat-dragonfly-amd64.tar.gz)
+- [FreeBSD amd64](/files/qchat/v0.0.2/qchat-freebsd-amd64.tar.gz)
+- [FreeBSD arm64](/files/qchat/v0.0.2/qchat-freebsd-arm64.tar.gz)
+- [NetBSD amd64](/files/qchat/v0.0.2/qchat-netbsd-amd64.tar.gz)
+- [NetBSD arm64](/files/qchat/v0.0.2/qchat-netbsd-arm64.tar.gz)
+- [OpenBSD amd64](/files/qchat/v0.0.2/qchat-openbsd-amd64.tar.gz)
+- [OpenBSD arm64](/files/qchat/v0.0.2/qchat-openbsd-arm64.tar.gz)
+- [Solaris amd64](/files/qchat/v0.0.2/qchat-solaris-amd64.tar.gz)
+
+## Установка с помощью Docker
+
+```
+docker volume create qchat_conf
+docker run -d --name qchat -p 1337:1337 -v qchat_conf:/etc/qchat gitrepo.ru/neonxp/qchat /app/qchat -config /etc/qchat/config.json
+```
+
+## Подключение к чату
+
+Для подключения к чату достаточно стандартного клиента ssh. Во всех адекватных
+ОС он есть из коробки. Для Windows - можно использовать Putty.
+
+```
+ssh [имя_пользователя@]хост -p 1337
+```
+
+Например, при локально запущенном чате:
+
+```
+ssh localhost -p 1337
+```
+
+Подойдёт любой эмулятор терминала совместимый с VT100.
+
+## Команды сервера
+
+Полную справку так же можно получить с помощью команды `/help`.
+
+- `/join [chan]` - подключиться к каналу [chan]. Если его нет, он будет создан.
+- `/chans` - список каналов
+- `/users` - список пользователей на сервере (не на канале, а именно на сервере)
+- `/me [message]` - отправка сообщения как бы от третьего лица
+
+## Форматирование сообщений
+
+- `*Полужирный*`
+- `+Курсив+`
+- `-Зачёркнутый текст-`
+- `_Подчёркнутый текст_`