[{"data":1,"prerenderedAt":848},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fru\u002Flifestyle\u002Fcode-review-kultur-izmerlyemye-kachestvo":13},{"i18nKey":4,"paths":5},"lifestyle-003-2026-06",{"de":6,"en":7,"es":8,"fr":9,"it":10,"ru":11,"tr":12},"\u002Fde\u002Flifestyle\u002Fcode-review-kultur-messbare-qualitat","\u002Fen\u002Flifestyle\u002Fcode-review-culture-measurable-quality","\u002Fes\u002Flifestyle\u002Fcultura-de-revision-de-codigo-calidad-medible-sin-conflictos-personales","\u002Ffr\u002Flifestyle\u002Fculture-examen-code-revue-qualite-mesurable","\u002Fit\u002Flifestyle\u002Fcultura-di-code-review-qualita-misurabile-senza-conflitti-personali","\u002Fru\u002Flifestyle\u002Fkod-inceleme-kulturu-olculebilir-kalite","\u002Ftr\u002Flifestyle\u002Fcode-review-kulturu-olculebilir-kalite-kisisel-catisma-yok",{"_path":14,"_dir":15,"_draft":16,"_partial":16,"_locale":17,"title":18,"description":19,"publishedAt":20,"modifiedAt":20,"category":15,"i18nKey":4,"tags":21,"readingTime":27,"author":28,"body":29,"_type":529,"_id":843,"_source":844,"_file":845,"_stem":846,"_extension":847},"\u002Fru\u002Flifestyle\u002Fcode-review-kultur-izmerlyemye-kachestvo","lifestyle",false,"","Культура Code Review: Измеряемое качество, без личных конфликтов","Time-to-review, плотность комментариев, размер PR — превратите командное качество в числовые метрики вместо субъективных суждений.","2026-06-20",[22,23,24,25,26],"code-review","engineering-culture","pr-metrics","team-workflow","async-first",9,"Roibase",{"type":30,"children":31,"toc":831},"root",[32,40,47,60,65,70,77,82,88,100,105,110,354,360,365,498,510,515,521,526,692,697,703,708,713,736,742,747,752,757,765,770,776,781,811,816,820,825],{"type":33,"tag":34,"props":35,"children":36},"element","p",{},[37],{"type":38,"value":39},"text","Процессы code review обычно начинаются как «контроль качества», а заканчиваются как «война эго». По мере роста команды проявляются две ловушки: PR'ы висят неделями без внимания, или каждый комментарий воспринимается как личная критика. Обе проблемы имеют одну корень — отсутствие измеряемых правил. За 8 лет работы в Roibase с командой из 15+ человек разных специальностей мы научились простому: пока вы не положите культуру review на числовые метрики, субъективное суждение неизбежно. Когда time-to-review, плотность комментариев и размер PR становятся системой, качество растёт, а конфликты падают.",{"type":33,"tag":41,"props":42,"children":44},"h2",{"id":43},"скорость-review-sla-time-to-review",[45],{"type":38,"value":46},"Скорость Review: SLA Time-to-Review",{"type":33,"tag":34,"props":48,"children":49},{},[50,52,58],{"type":38,"value":51},"Каждый PR имеет жизненный цикл. Время от открытия до первого комментария — time-to-first-review — первый индикатор дисциплины команды. В Roibase мы установили максимум ",{"type":33,"tag":53,"props":54,"children":55},"strong",{},[56],{"type":38,"value":57},"4 часа",{"type":38,"value":59}," (в рабочее время). Почему 4 часа? Это сладкое пятно между сохранением блоков deep work и ускорением цикла обратной связи в асинхронной модели.",{"type":33,"tag":34,"props":61,"children":62},{},[63],{"type":38,"value":64},"Правило: первый reviewer должен посмотреть PR в течение 4 часов после открытия. Механизм enforcement — не Slack-уведомление, а GitHub Actions workflow. При открытии PR автоматически ставится тег, через 4 часа назначенному reviewer'у приходит Slack-упоминание. Это мягкое напоминание, которое исключает забытые review'ы.",{"type":33,"tag":34,"props":66,"children":67},{},[68],{"type":38,"value":69},"Метрика time-to-merge ещё критичнее. Время от открытия PR до merge в main ветку — например, для backend-изменений: максимум 24 часа. Для frontend: 48 часов. Почему разница? Backend-merge'ы обычно требуют меньше визуальной проверки, могут быть задеплоены за feature flag. Frontend требует design QA и кросс-браузерных тестов.",{"type":33,"tag":71,"props":72,"children":74},"h3",{"id":73},"метрика-дашборд-linear-integration",[75],{"type":38,"value":76},"Метрика-дашборд: Linear Integration",{"type":33,"tag":34,"props":78,"children":79},{},[80],{"type":38,"value":81},"Мы интегрируем Linear с GitHub — каждый PR автоматически связывается с Linear-тикетом. Статус тикета обновляется в соответствии с PR lifecycle. В конце спринта смотрим метрику: средний time-to-merge. Если команда превышает 36 часов, на ретроспективе это проблема для обсуждения — обычно это PR size или нагрузка на reviewer'ов.",{"type":33,"tag":41,"props":83,"children":85},{"id":84},"размер-pr-правило-400-строк",[86],{"type":38,"value":87},"Размер PR: Правило 400 строк",{"type":33,"tag":34,"props":89,"children":90},{},[91,93,98],{"type":38,"value":92},"Большие PR'ы нельзя review'ить. Это наиболее частый консенсус в индустрии, но редко превращается в измеряемое правило. Стандарт Roibase: ",{"type":33,"tag":53,"props":94,"children":95},{},[96],{"type":38,"value":97},"максимум 400 строк изменений",{"type":38,"value":99}," (сумма additions + deletions). Откуда это число? Это объем кода, который reviewer может логично держать в голове при 30-минутном focused review.",{"type":33,"tag":34,"props":101,"children":102},{},[103],{"type":38,"value":104},"Чтобы enforce'ить правило, используем GitHub branch protection: PR с более чем 400 строк получает автоматический label «needs-split» и не может быть смёрджен. Есть исключения — обновления зависимостей, migration-скрипты. Но даже они требуют manual override с обоснованием в комментарии GitHub.",{"type":33,"tag":34,"props":106,"children":107},{},[108],{"type":38,"value":109},"Как делать большие рефакторинги? Stacked PR'ы. Первый PR: изменение интерфейса, второй: реализация, третий: удаление старого кода. Каждый под 400 строк, каждый можно review'ить отдельно. Это медленнее? Да. Риск merge conflict'ов выше? Немного. Но quality review'я улучшается кратно — reviewer может действительно понять каждое изменение.",{"type":33,"tag":111,"props":112,"children":116},"pre",{"code":113,"language":114,"meta":17,"className":115,"style":17},"# GitHub Actions — PR size check\nname: PR Size Check\non: pull_request\n\njobs:\n  size_check:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check PR size\n        run: |\n          ADDITIONS=$(jq '.pull_request.additions' \"$GITHUB_EVENT_PATH\")\n          DELETIONS=$(jq '.pull_request.deletions' \"$GITHUB_EVENT_PATH\")\n          TOTAL=$((ADDITIONS + DELETIONS))\n          if [ $TOTAL -gt 400 ]; then\n            echo \"PR too large: $TOTAL lines\"\n            gh pr edit --add-label needs-split\n            exit 1\n          fi\n","yaml","language-yaml shiki shiki-themes github-dark",[117],{"type":33,"tag":118,"props":119,"children":120},"code",{"__ignoreMap":17},[121,133,155,174,184,198,211,229,242,263,282,291,300,309,318,327,336,345],{"type":33,"tag":122,"props":123,"children":126},"span",{"class":124,"line":125},"line",1,[127],{"type":33,"tag":122,"props":128,"children":130},{"style":129},"--shiki-default:#6A737D",[131],{"type":38,"value":132},"# GitHub Actions — PR size check\n",{"type":33,"tag":122,"props":134,"children":136},{"class":124,"line":135},2,[137,143,149],{"type":33,"tag":122,"props":138,"children":140},{"style":139},"--shiki-default:#85E89D",[141],{"type":38,"value":142},"name",{"type":33,"tag":122,"props":144,"children":146},{"style":145},"--shiki-default:#E1E4E8",[147],{"type":38,"value":148},": ",{"type":33,"tag":122,"props":150,"children":152},{"style":151},"--shiki-default:#9ECBFF",[153],{"type":38,"value":154},"PR Size Check\n",{"type":33,"tag":122,"props":156,"children":158},{"class":124,"line":157},3,[159,165,169],{"type":33,"tag":122,"props":160,"children":162},{"style":161},"--shiki-default:#79B8FF",[163],{"type":38,"value":164},"on",{"type":33,"tag":122,"props":166,"children":167},{"style":145},[168],{"type":38,"value":148},{"type":33,"tag":122,"props":170,"children":171},{"style":151},[172],{"type":38,"value":173},"pull_request\n",{"type":33,"tag":122,"props":175,"children":177},{"class":124,"line":176},4,[178],{"type":33,"tag":122,"props":179,"children":181},{"emptyLinePlaceholder":180},true,[182],{"type":38,"value":183},"\n",{"type":33,"tag":122,"props":185,"children":187},{"class":124,"line":186},5,[188,193],{"type":33,"tag":122,"props":189,"children":190},{"style":139},[191],{"type":38,"value":192},"jobs",{"type":33,"tag":122,"props":194,"children":195},{"style":145},[196],{"type":38,"value":197},":\n",{"type":33,"tag":122,"props":199,"children":201},{"class":124,"line":200},6,[202,207],{"type":33,"tag":122,"props":203,"children":204},{"style":139},[205],{"type":38,"value":206},"  size_check",{"type":33,"tag":122,"props":208,"children":209},{"style":145},[210],{"type":38,"value":197},{"type":33,"tag":122,"props":212,"children":214},{"class":124,"line":213},7,[215,220,224],{"type":33,"tag":122,"props":216,"children":217},{"style":139},[218],{"type":38,"value":219},"    runs-on",{"type":33,"tag":122,"props":221,"children":222},{"style":145},[223],{"type":38,"value":148},{"type":33,"tag":122,"props":225,"children":226},{"style":151},[227],{"type":38,"value":228},"ubuntu-latest\n",{"type":33,"tag":122,"props":230,"children":232},{"class":124,"line":231},8,[233,238],{"type":33,"tag":122,"props":234,"children":235},{"style":139},[236],{"type":38,"value":237},"    steps",{"type":33,"tag":122,"props":239,"children":240},{"style":145},[241],{"type":38,"value":197},{"type":33,"tag":122,"props":243,"children":244},{"class":124,"line":27},[245,250,254,258],{"type":33,"tag":122,"props":246,"children":247},{"style":145},[248],{"type":38,"value":249},"      - ",{"type":33,"tag":122,"props":251,"children":252},{"style":139},[253],{"type":38,"value":142},{"type":33,"tag":122,"props":255,"children":256},{"style":145},[257],{"type":38,"value":148},{"type":33,"tag":122,"props":259,"children":260},{"style":151},[261],{"type":38,"value":262},"Check PR size\n",{"type":33,"tag":122,"props":264,"children":266},{"class":124,"line":265},10,[267,272,276],{"type":33,"tag":122,"props":268,"children":269},{"style":139},[270],{"type":38,"value":271},"        run",{"type":33,"tag":122,"props":273,"children":274},{"style":145},[275],{"type":38,"value":148},{"type":33,"tag":122,"props":277,"children":279},{"style":278},"--shiki-default:#F97583",[280],{"type":38,"value":281},"|\n",{"type":33,"tag":122,"props":283,"children":285},{"class":124,"line":284},11,[286],{"type":33,"tag":122,"props":287,"children":288},{"style":151},[289],{"type":38,"value":290},"          ADDITIONS=$(jq '.pull_request.additions' \"$GITHUB_EVENT_PATH\")\n",{"type":33,"tag":122,"props":292,"children":294},{"class":124,"line":293},12,[295],{"type":33,"tag":122,"props":296,"children":297},{"style":151},[298],{"type":38,"value":299},"          DELETIONS=$(jq '.pull_request.deletions' \"$GITHUB_EVENT_PATH\")\n",{"type":33,"tag":122,"props":301,"children":303},{"class":124,"line":302},13,[304],{"type":33,"tag":122,"props":305,"children":306},{"style":151},[307],{"type":38,"value":308},"          TOTAL=$((ADDITIONS + DELETIONS))\n",{"type":33,"tag":122,"props":310,"children":312},{"class":124,"line":311},14,[313],{"type":33,"tag":122,"props":314,"children":315},{"style":151},[316],{"type":38,"value":317},"          if [ $TOTAL -gt 400 ]; then\n",{"type":33,"tag":122,"props":319,"children":321},{"class":124,"line":320},15,[322],{"type":33,"tag":122,"props":323,"children":324},{"style":151},[325],{"type":38,"value":326},"            echo \"PR too large: $TOTAL lines\"\n",{"type":33,"tag":122,"props":328,"children":330},{"class":124,"line":329},16,[331],{"type":33,"tag":122,"props":332,"children":333},{"style":151},[334],{"type":38,"value":335},"            gh pr edit --add-label needs-split\n",{"type":33,"tag":122,"props":337,"children":339},{"class":124,"line":338},17,[340],{"type":33,"tag":122,"props":341,"children":342},{"style":151},[343],{"type":38,"value":344},"            exit 1\n",{"type":33,"tag":122,"props":346,"children":348},{"class":124,"line":347},18,[349],{"type":33,"tag":122,"props":350,"children":351},{"style":151},[352],{"type":38,"value":353},"          fi\n",{"type":33,"tag":41,"props":355,"children":357},{"id":356},"плотность-комментариев-граница-нитпикинга",[358],{"type":38,"value":359},"Плотность комментариев: Граница нитпикинга",{"type":33,"tag":34,"props":361,"children":362},{},[363],{"type":38,"value":364},"Не все комментарии равны. «Это можно рефакторить» и «Здесь может быть null pointer exception» — разные по критичности. В шаблоне review'а Roibase категории комментариев обязательны:",{"type":33,"tag":366,"props":367,"children":368},"table",{},[369,393],{"type":33,"tag":370,"props":371,"children":372},"thead",{},[373],{"type":33,"tag":374,"props":375,"children":376},"tr",{},[377,383,388],{"type":33,"tag":378,"props":379,"children":380},"th",{},[381],{"type":38,"value":382},"Категория",{"type":33,"tag":378,"props":384,"children":385},{},[386],{"type":38,"value":387},"Tag",{"type":33,"tag":378,"props":389,"children":390},{},[391],{"type":38,"value":392},"Пример",{"type":33,"tag":394,"props":395,"children":396},"tbody",{},[397,423,448,473],{"type":33,"tag":374,"props":398,"children":399},{},[400,409,418],{"type":33,"tag":401,"props":402,"children":403},"td",{},[404],{"type":33,"tag":53,"props":405,"children":406},{},[407],{"type":38,"value":408},"Blocker",{"type":33,"tag":401,"props":410,"children":411},{},[412],{"type":33,"tag":118,"props":413,"children":415},{"className":414},[],[416],{"type":38,"value":417},"🔴 BLOCKER",{"type":33,"tag":401,"props":419,"children":420},{},[421],{"type":38,"value":422},"Уязвимость, crash при runtime",{"type":33,"tag":374,"props":424,"children":425},{},[426,434,443],{"type":33,"tag":401,"props":427,"children":428},{},[429],{"type":33,"tag":53,"props":430,"children":431},{},[432],{"type":38,"value":433},"Major",{"type":33,"tag":401,"props":435,"children":436},{},[437],{"type":33,"tag":118,"props":438,"children":440},{"className":439},[],[441],{"type":38,"value":442},"🟠 MAJOR",{"type":33,"tag":401,"props":444,"children":445},{},[446],{"type":38,"value":447},"Регрессия производительности, ошибка логики",{"type":33,"tag":374,"props":449,"children":450},{},[451,459,468],{"type":33,"tag":401,"props":452,"children":453},{},[454],{"type":33,"tag":53,"props":455,"children":456},{},[457],{"type":38,"value":458},"Minor",{"type":33,"tag":401,"props":460,"children":461},{},[462],{"type":33,"tag":118,"props":463,"children":465},{"className":464},[],[466],{"type":38,"value":467},"🟡 MINOR",{"type":33,"tag":401,"props":469,"children":470},{},[471],{"type":38,"value":472},"Соглашение о именовании, покрытие тестами",{"type":33,"tag":374,"props":474,"children":475},{},[476,484,493],{"type":33,"tag":401,"props":477,"children":478},{},[479],{"type":33,"tag":53,"props":480,"children":481},{},[482],{"type":38,"value":483},"Nitpick",{"type":33,"tag":401,"props":485,"children":486},{},[487],{"type":33,"tag":118,"props":488,"children":490},{"className":489},[],[491],{"type":38,"value":492},"🔵 NITPICK",{"type":33,"tag":401,"props":494,"children":495},{},[496],{"type":38,"value":497},"Вопрос вкуса, субъективно",{"type":33,"tag":34,"props":499,"children":500},{},[501,503,508],{"type":38,"value":502},"Правило: ",{"type":33,"tag":53,"props":504,"children":505},{},[506],{"type":38,"value":507},"нитпик не более 30%",{"type":38,"value":509},". Если в PR 10 комментариев, не больше 3 нитпик'ов, остальное — blocker\u002Fmajor\u002Fminor. Почему? Потому что review с преобладанием нитпик'ов снижает мотивацию автора, reviewer'а воспринимают как излишне придирчивого.",{"type":33,"tag":34,"props":511,"children":512},{},[513],{"type":38,"value":514},"Метрика плотности комментариев: среднее число комментариев на PR. В Roibase это 3-5. Свыше 10 — обычно сигнал, что PR нужно разбить. Ноль комментариев — сигнал rubber stamp review.",{"type":33,"tag":71,"props":516,"children":518},{"id":517},"использование-шаблона",[519],{"type":38,"value":520},"Использование шаблона",{"type":33,"tag":34,"props":522,"children":523},{},[524],{"type":38,"value":525},"Каждый reviewer начинает с шаблона GitHub PR:",{"type":33,"tag":111,"props":527,"children":531},{"code":528,"language":529,"meta":17,"className":530,"style":17},"## Чек-лист Review\n- [ ] Логика кода верна?\n- [ ] Покрытие тестами выше 80%?\n- [ ] Есть breaking change? (CHANGELOG обновлён?)\n- [ ] Измерено влияние на производительность? (benchmarks\u002F)\n\n## Комментарии\n**🔴 BLOCKER:**\n-\n\n**🟠 MAJOR:**\n-\n\n**🟡 MINOR:**\n-\n\n**🔵 NITPICK:**\n-\n","markdown","language-markdown shiki shiki-themes github-dark",[532],{"type":33,"tag":118,"props":533,"children":534},{"__ignoreMap":17},[535,544,558,570,582,594,601,609,618,626,633,641,648,655,663,670,677,685],{"type":33,"tag":122,"props":536,"children":537},{"class":124,"line":125},[538],{"type":33,"tag":122,"props":539,"children":541},{"style":540},"--shiki-default:#79B8FF;--shiki-default-font-weight:bold",[542],{"type":38,"value":543},"## Чек-лист Review\n",{"type":33,"tag":122,"props":545,"children":546},{"class":124,"line":135},[547,553],{"type":33,"tag":122,"props":548,"children":550},{"style":549},"--shiki-default:#FFAB70",[551],{"type":38,"value":552},"-",{"type":33,"tag":122,"props":554,"children":555},{"style":145},[556],{"type":38,"value":557}," [ ] Логика кода верна?\n",{"type":33,"tag":122,"props":559,"children":560},{"class":124,"line":157},[561,565],{"type":33,"tag":122,"props":562,"children":563},{"style":549},[564],{"type":38,"value":552},{"type":33,"tag":122,"props":566,"children":567},{"style":145},[568],{"type":38,"value":569}," [ ] Покрытие тестами выше 80%?\n",{"type":33,"tag":122,"props":571,"children":572},{"class":124,"line":176},[573,577],{"type":33,"tag":122,"props":574,"children":575},{"style":549},[576],{"type":38,"value":552},{"type":33,"tag":122,"props":578,"children":579},{"style":145},[580],{"type":38,"value":581}," [ ] Есть breaking change? (CHANGELOG обновлён?)\n",{"type":33,"tag":122,"props":583,"children":584},{"class":124,"line":186},[585,589],{"type":33,"tag":122,"props":586,"children":587},{"style":549},[588],{"type":38,"value":552},{"type":33,"tag":122,"props":590,"children":591},{"style":145},[592],{"type":38,"value":593}," [ ] Измерено влияние на производительность? (benchmarks\u002F)\n",{"type":33,"tag":122,"props":595,"children":596},{"class":124,"line":200},[597],{"type":33,"tag":122,"props":598,"children":599},{"emptyLinePlaceholder":180},[600],{"type":38,"value":183},{"type":33,"tag":122,"props":602,"children":603},{"class":124,"line":213},[604],{"type":33,"tag":122,"props":605,"children":606},{"style":540},[607],{"type":38,"value":608},"## Комментарии\n",{"type":33,"tag":122,"props":610,"children":611},{"class":124,"line":231},[612],{"type":33,"tag":122,"props":613,"children":615},{"style":614},"--shiki-default:#E1E4E8;--shiki-default-font-weight:bold",[616],{"type":38,"value":617},"**🔴 BLOCKER:**\n",{"type":33,"tag":122,"props":619,"children":620},{"class":124,"line":27},[621],{"type":33,"tag":122,"props":622,"children":623},{"style":145},[624],{"type":38,"value":625},"-\n",{"type":33,"tag":122,"props":627,"children":628},{"class":124,"line":265},[629],{"type":33,"tag":122,"props":630,"children":631},{"emptyLinePlaceholder":180},[632],{"type":38,"value":183},{"type":33,"tag":122,"props":634,"children":635},{"class":124,"line":284},[636],{"type":33,"tag":122,"props":637,"children":638},{"style":614},[639],{"type":38,"value":640},"**🟠 MAJOR:**\n",{"type":33,"tag":122,"props":642,"children":643},{"class":124,"line":293},[644],{"type":33,"tag":122,"props":645,"children":646},{"style":145},[647],{"type":38,"value":625},{"type":33,"tag":122,"props":649,"children":650},{"class":124,"line":302},[651],{"type":33,"tag":122,"props":652,"children":653},{"emptyLinePlaceholder":180},[654],{"type":38,"value":183},{"type":33,"tag":122,"props":656,"children":657},{"class":124,"line":311},[658],{"type":33,"tag":122,"props":659,"children":660},{"style":614},[661],{"type":38,"value":662},"**🟡 MINOR:**\n",{"type":33,"tag":122,"props":664,"children":665},{"class":124,"line":320},[666],{"type":33,"tag":122,"props":667,"children":668},{"style":145},[669],{"type":38,"value":625},{"type":33,"tag":122,"props":671,"children":672},{"class":124,"line":329},[673],{"type":33,"tag":122,"props":674,"children":675},{"emptyLinePlaceholder":180},[676],{"type":38,"value":183},{"type":33,"tag":122,"props":678,"children":679},{"class":124,"line":338},[680],{"type":33,"tag":122,"props":681,"children":682},{"style":614},[683],{"type":38,"value":684},"**🔵 NITPICK:**\n",{"type":33,"tag":122,"props":686,"children":687},{"class":124,"line":347},[688],{"type":33,"tag":122,"props":689,"children":690},{"style":145},[691],{"type":38,"value":625},{"type":33,"tag":34,"props":693,"children":694},{},[695],{"type":38,"value":696},"Этот шаблон делает две вещи: заставляет reviewer'а категоризировать, позволяет author'у быстро увидеть, какие комментарии критичны.",{"type":33,"tag":41,"props":698,"children":700},{"id":699},"асинхронный-review-ловушка-синхронных-встреч",[701],{"type":38,"value":702},"Асинхронный Review: Ловушка синхронных встреч",{"type":33,"tag":34,"props":704,"children":705},{},[706],{"type":38,"value":707},"Code review не должен происходить на синхронных встречах. В Roibase нет концепции «review call» — все review'ы асинхронные, через GitHub. Почему? Команда работает в 3 разных timezone'ах, сохранение блоков deep work критично.",{"type":33,"tag":34,"props":709,"children":710},{},[711],{"type":38,"value":712},"Дисциплина асинхронного review: reviewer смотрит PR в своё время глубокой концентрации (обычно 09:00-12:00). Пишет комментарии, approve или request changes. Author получает уведомление (в своём календаре), вносит изменения, re-request'ит review. Цикл обычно повторяется 2-3 раза.",{"type":33,"tag":34,"props":714,"children":715},{},[716,718,723,725,734],{"type":38,"value":717},"Исключение: ",{"type":33,"tag":53,"props":719,"children":720},{},[721],{"type":38,"value":722},"review deadlock",{"type":38,"value":724}," — author и reviewer не договорились за 3 итерации, тогда открывается 15-минутный sync call. Но это происходит 5-6 раз в год, это исключение. ",{"type":33,"tag":726,"props":727,"children":731},"a",{"href":728,"rel":729},"https:\u002F\u002Fwww.roibase.com.tr\u002Fru\u002Fbranding",[730],"nofollow",[732],{"type":38,"value":733},"Фирменный стиль",{"type":38,"value":735}," Roibase, созданный в процессе брендирования, отражает эту культуру async-first — documentation-first, meeting-last.",{"type":33,"tag":41,"props":737,"children":739},{"id":738},"ownership-vs-gatekeeping",[740],{"type":38,"value":741},"Ownership vs. Gatekeeping",{"type":33,"tag":34,"props":743,"children":744},{},[745],{"type":38,"value":746},"Целью code review является quality assurance, но побочный эффект не должен быть gatekeeping. В Roibase каждый PR требует минимум 1, максимум 2 reviewer'ов. Почему максимум 2? Потому что ожидание 3+ approval'ов имеет временную стоимость больше, чем выигрыш в quality.",{"type":33,"tag":34,"props":748,"children":749},{},[750],{"type":38,"value":751},"Выбор reviewer'а не автоматический — выбирает author. Правило: минимум один из code owners (из CODEOWNERS файла), второй — кто угодно. Этот подход держит ownership в author'е. Вопрос «кто должен approve?» — ответственность author'а, не лидера.",{"type":33,"tag":34,"props":753,"children":754},{},[755],{"type":38,"value":756},"Файл CODEOWNERS выглядит так:",{"type":33,"tag":111,"props":758,"children":760},{"code":759},"# Backend\n\u002Fbackend\u002F @backend-team\n\u002Fapi\u002F @backend-team\n\n# Frontend\n\u002Fweb\u002F @frontend-team\n\u002Fmobile\u002F @mobile-team\n\n# Infrastructure\n\u002Fterraform\u002F @devops-team\n\u002F.github\u002F @devops-team\n",[761],{"type":33,"tag":118,"props":762,"children":763},{"__ignoreMap":17},[764],{"type":38,"value":759},{"type":33,"tag":34,"props":766,"children":767},{},[768],{"type":38,"value":769},"Каждое изменение файла должно быть review'но кем-то из соответствующей team'ы — но author выбирает конкретного человека.",{"type":33,"tag":41,"props":771,"children":773},{"id":772},"ретроспектива-метрики-review",[774],{"type":38,"value":775},"Ретроспектива: Метрики Review",{"type":33,"tag":34,"props":777,"children":778},{},[779],{"type":38,"value":780},"В конце каждого спринта (каждые 2 недели) смотрим на метрики review. Linear дашборд показывает:",{"type":33,"tag":782,"props":783,"children":784},"ul",{},[785,791,796,801,806],{"type":33,"tag":786,"props":787,"children":788},"li",{},[789],{"type":38,"value":790},"Средний time-to-merge (цель: 36 часов)",{"type":33,"tag":786,"props":792,"children":793},{},[794],{"type":38,"value":795},"Распределение размера PR (цель: 90% под 400 строк)",{"type":33,"tag":786,"props":797,"children":798},{},[799],{"type":38,"value":800},"Плотность комментариев (цель: 3-5 на PR)",{"type":33,"tag":786,"props":802,"children":803},{},[804],{"type":38,"value":805},"Доля нитпик'ов (цель: \u003C30%)",{"type":33,"tag":786,"props":807,"children":808},{},[809],{"type":38,"value":810},"Review bottleneck (кто чаще всего ждёт review?)",{"type":33,"tag":34,"props":812,"children":813},{},[814],{"type":38,"value":815},"Эти числа обсуждаются на ретроспективе, но без личных обвинений. Вместо «Алёша медленно review'ит» звучит: «Backend PR'ы ждут в среднем 48 часов, нужно ли расширить пул reviewer'ов?»",{"type":33,"tag":817,"props":818,"children":819},"hr",{},[],{"type":33,"tag":34,"props":821,"children":822},{},[823],{"type":38,"value":824},"Превратить культуру code review из личных суждений в системную дисциплину несложно — нужны только измеряемые правила. SLA time-to-review, правило 400 строк, категории комментариев, async-first подход — это конкретные инструменты, которые 8 лет помогали Roibase сохранять качество при росте команды. Если ваши review'ы всё ещё работают «интуитивно» и «в зависимости от ситуации» — положите цифры, систематизируйте. Качество вырастет, конфликты упадут.",{"type":33,"tag":826,"props":827,"children":828},"style",{},[829],{"type":38,"value":830},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":17,"searchDepth":157,"depth":157,"links":832},[833,836,837,840,841,842],{"id":43,"depth":135,"text":46,"children":834},[835],{"id":73,"depth":157,"text":76},{"id":84,"depth":135,"text":87},{"id":356,"depth":135,"text":359,"children":838},[839],{"id":517,"depth":157,"text":520},{"id":699,"depth":135,"text":702},{"id":738,"depth":135,"text":741},{"id":772,"depth":135,"text":775},"content:ru:lifestyle:code-review-kultur-izmerlyemye-kachestvo.md","content","ru\u002Flifestyle\u002Fcode-review-kultur-izmerlyemye-kachestvo.md","ru\u002Flifestyle\u002Fcode-review-kultur-izmerlyemye-kachestvo","md",1782079494661]