[{"data":1,"prerenderedAt":1191},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fru\u002Flifestyle\u002Fcode-review-kulturi-olculebilir-kalite":13},{"i18nKey":4,"paths":5},"lifestyle-003-2026-05",{"de":6,"en":7,"es":8,"fr":9,"it":10,"ru":11,"tr":12},"\u002Fde\u002Flifestyle\u002Fcode-review-kultur-messbare-qualitaet","\u002Fen\u002Flifestyle\u002Fcode-review-culture-measurable-quality-no-personal-conflict","\u002Fes\u002Flifestyle\u002Fcodigo-resena-cultura-calidad-medible-sin-conflictos-personales","\u002Ffr\u002Flifestyle\u002Fculture-revue-code-qualite-mesurable","\u002Fit\u002Flifestyle\u002Fcode-review-kultur-olculebilir-kalite","\u002Fru\u002Flifestyle\u002Fcode-review-kulturi-olculebilir-kalite","\u002Ftr\u002Flifestyle\u002Fcode-review-kulturu-olculebilir-kalite-kisisel-catisma-yok",{"_path":11,"_dir":14,"_draft":15,"_partial":15,"_locale":16,"title":17,"description":18,"publishedAt":19,"modifiedAt":19,"category":14,"i18nKey":4,"tags":20,"readingTime":26,"author":27,"body":28,"_type":1185,"_id":1186,"_source":1187,"_file":1188,"_stem":1189,"_extension":1190},"lifestyle",false,"","Культура Code Review: Измеримое Качество, Без Личных Конфликтов","Руководство по трансформации процесса code review из субъективных комментариев в измеримые командные стандарты через time-to-review, comment density и PR size правила.","2026-05-13",[21,22,23,24,25],"code-review","engineering-culture","team-workflow","quality-metrics","async-collaboration",8,"Roibase",{"type":29,"children":30,"toc":1174},"root",[31,39,46,68,73,109,114,130,136,141,146,233,243,253,263,270,275,281,286,294,337,342,376,386,392,397,440,445,1004,1009,1015,1020,1028,1051,1059,1082,1087,1093,1098,1106,1114,1119,1127,1132,1142,1152,1157,1163,1168],{"type":32,"tag":33,"props":34,"children":35},"element","p",{},[36],{"type":37,"value":38},"text","Code review считается «конструктивной критикой», но на практике более 60% команд тратят время на субъективные дебаты. На PR приходит 15 комментариев: 8 об оформлении, 3 о предпочтениях архитектуры, а 2 указывают на реальные баги. Главная проблема: между личным вкусом и командным стандартом нет чётких границ. 8+ лет опыта руководства командой в Roibase показали: если качество review не поддаётся измерению, оно превращается в личный конфликт. В этой статье разберём, как трансформировать процесс code review в систематическую культуру через численные правила — time-to-review, comment density и PR size.",{"type":32,"tag":40,"props":41,"children":43},"h2",{"id":42},"от-субъективного-мнения-к-систематическому-стандарту",[44],{"type":37,"value":45},"От субъективного мнения к систематическому стандарту",{"type":32,"tag":33,"props":47,"children":48},{},[49,51,58,60,66],{"type":37,"value":50},"В code review фразы вроде «мне кажется», «можно лучше», «это не идеально» замедляют работу. Типичный сценарий: backend-разработчик отклоняет код, где используется ",{"type":32,"tag":52,"props":53,"children":55},"code",{"className":54},[],[56],{"type":37,"value":57},"forEach()",{"type":37,"value":59}," вместо ",{"type":32,"tag":52,"props":61,"children":63},{"className":62},[],[64],{"type":37,"value":65},"map()",{"type":37,"value":67},", frontend-разработчик возражает «прирост производительности 0,2% — не оптимизируем», идёт 6 сообщений туда-сюда. 45 минут потеряно, решения нет.",{"type":32,"tag":33,"props":69,"children":70},{},[71],{"type":37,"value":72},"Решение: сделайте критерии review измеримыми. Вместо определения «плохой код» установите численные пороги. Например, в Roibase стандарты таковы:",{"type":32,"tag":74,"props":75,"children":76},"ul",{},[77,89,99],{"type":32,"tag":78,"props":79,"children":80},"li",{},[81,87],{"type":32,"tag":82,"props":83,"children":84},"strong",{},[85],{"type":37,"value":86},"Cyclomatic complexity >10:",{"type":37,"value":88}," автоматический отказ (проверка SonarQube)",{"type":32,"tag":78,"props":90,"children":91},{},[92,97],{"type":32,"tag":82,"props":93,"children":94},{},[95],{"type":37,"value":96},"Снижение test coverage >5%:",{"type":37,"value":98}," обязательный ручной review",{"type":32,"tag":78,"props":100,"children":101},{},[102,107],{"type":32,"tag":82,"props":103,"children":104},{},[105],{"type":37,"value":106},"Длина функции >50 строк:",{"type":37,"value":108}," требуется комментарий (нужна документация исключения)",{"type":32,"tag":33,"props":110,"children":111},{},[112],{"type":37,"value":113},"Эти правила enforcement'нуются в linter'е. Reviewer не говорит «мне кажется, слишком длинная», система сообщает «49 строк — принято, 51 строка — требуется объяснение». Дискуссии исчезают, работает стандарт. За 2 месяца истории PR'ов в команде процент отказов упал с 12% на 4%, потому что исчезли субъективные реджекты.",{"type":32,"tag":33,"props":115,"children":116},{},[117,119,128],{"type":37,"value":118},"Важное замечание: этот системный подход похож на процесс ",{"type":32,"tag":120,"props":121,"children":125},"a",{"href":122,"rel":123},"https:\u002F\u002Fwww.roibase.com.tr\u002Fru\u002Fbranding",[124],"nofollow",[126],{"type":37,"value":127},"брендирования и формирования идентичности бренда",{"type":37,"value":129}," — согласованность приходит не из личных предпочтений, а из измеримых критериев. Если палитра цветов вашего бренда задана hex-кодами, то качество вашего кода должно определяться численными метриками.",{"type":32,"tag":40,"props":131,"children":133},{"id":132},"time-to-review-дисциплина-ответа-в-асинхронных-командах",[134],{"type":37,"value":135},"Time-to-review: дисциплина ответа в асинхронных командах",{"type":32,"tag":33,"props":137,"children":138},{},[139],{"type":37,"value":140},"Если ваша команда работает в режиме remote + async, задержки review — главное узкое место. Среднестатистический показатель: 18 часов на первый review (GitHub 2024 отчёт). За эти 18 часов автор PR либо блокируется, либо начинает другую работу — оба варианта дорогостоящи.",{"type":32,"tag":33,"props":142,"children":143},{},[144],{"type":37,"value":145},"Workflow Roibase:",{"type":32,"tag":147,"props":148,"children":149},"table",{},[150,174],{"type":32,"tag":151,"props":152,"children":153},"thead",{},[154],{"type":32,"tag":155,"props":156,"children":157},"tr",{},[158,164,169],{"type":32,"tag":159,"props":160,"children":161},"th",{},[162],{"type":37,"value":163},"Метрика",{"type":32,"tag":159,"props":165,"children":166},{},[167],{"type":37,"value":168},"Порог",{"type":32,"tag":159,"props":170,"children":171},{},[172],{"type":37,"value":173},"Механизм",{"type":32,"tag":175,"props":176,"children":177},"tbody",{},[178,197,215],{"type":32,"tag":155,"props":179,"children":180},{},[181,187,192],{"type":32,"tag":182,"props":183,"children":184},"td",{},[185],{"type":37,"value":186},"Time-to-first-review",{"type":32,"tag":182,"props":188,"children":189},{},[190],{"type":37,"value":191},"\u003C4 часа",{"type":32,"tag":182,"props":193,"children":194},{},[195],{"type":37,"value":196},"Slack-уведомление",{"type":32,"tag":155,"props":198,"children":199},{},[200,205,210],{"type":32,"tag":182,"props":201,"children":202},{},[203],{"type":37,"value":204},"Time-to-merge (после approval)",{"type":32,"tag":182,"props":206,"children":207},{},[208],{"type":37,"value":209},"\u003C2 часа",{"type":32,"tag":182,"props":211,"children":212},{},[213],{"type":37,"value":214},"Блокировка pipeline",{"type":32,"tag":155,"props":216,"children":217},{},[218,223,228],{"type":32,"tag":182,"props":219,"children":220},{},[221],{"type":37,"value":222},"Раундов review в одном PR",{"type":32,"tag":182,"props":224,"children":225},{},[226],{"type":37,"value":227},"\u003C3",{"type":32,"tag":182,"props":229,"children":230},{},[231],{"type":37,"value":232},"Предложение разделить PR",{"type":32,"tag":33,"props":234,"children":235},{},[236,241],{"type":32,"tag":82,"props":237,"children":238},{},[239],{"type":37,"value":240},"4-часовой порог первого review:",{"type":37,"value":242}," когда PR открывается, в Slack идёт mention, если первый комментарий не появляется за 4 часа — идёт escalation notification. Это не значит «срочно смотрите» — в асинхронном режиме проверять очередь review каждые 4 часа — это дисциплина.",{"type":32,"tag":33,"props":244,"children":245},{},[246,251],{"type":32,"tag":82,"props":247,"children":248},{},[249],{"type":37,"value":250},"2-часовой порог merge:",{"type":37,"value":252}," после approval PR должен merge'иться в течение 2 часов, иначе автоматический merge срабатывает (если тесты pass + есть approval). Это убивает сценарий «забытого PR».",{"type":32,"tag":33,"props":254,"children":255},{},[256,261],{"type":32,"tag":82,"props":257,"children":258},{},[259],{"type":37,"value":260},"Правило 3 раундов:",{"type":37,"value":262}," если в PR открывается 3-й раунд обсуждения, значит либо PR слишком большой, либо scope неясен. Система автоматически предлагает разделить PR. Так 300 строк становятся 2×150, review завершается быстрее.",{"type":32,"tag":264,"props":265,"children":267},"h3",{"id":266},"пример-асинхронного-protocolа",[268],{"type":37,"value":269},"Пример асинхронного protocol'а",{"type":32,"tag":33,"props":271,"children":272},{},[273],{"type":37,"value":274},"Developer A открывает PR в 09:00. Developer B делает review в 13:30 (4 часа спустя). A исправляет в 18:00. B делает финальную проверку в 09:30 следующего дня. Всего 24,5 часа процесса, но ни одной синхронной встречи, никто не блокирован. Time-to-merge: 1,5 рабочих дня. Это отличная скорость для асинхронной культуры.",{"type":32,"tag":40,"props":276,"children":278},{"id":277},"размер-pr-и-comment-density-большой-pr-плохой-pr",[279],{"type":37,"value":280},"Размер PR и comment density: большой PR — плохой PR",{"type":32,"tag":33,"props":282,"children":283},{},[284],{"type":37,"value":285},"Большой PR нельзя полноценно review'ить. GitHub данные: в PR'ах с изменением >400 строк внимание reviewer'а падает на 12 минут (в 200-строковом PR — 28 минут). То есть в 2 раза больше кода — в 2,3 раза меньше внимания.",{"type":32,"tag":33,"props":287,"children":288},{},[289],{"type":32,"tag":82,"props":290,"children":291},{},[292],{"type":37,"value":293},"Правило размера PR:",{"type":32,"tag":74,"props":295,"children":296},{},[297,307,317,327],{"type":32,"tag":78,"props":298,"children":299},{},[300,305],{"type":32,"tag":82,"props":301,"children":302},{},[303],{"type":37,"value":304},"Маленький (0-100 строк):",{"type":37,"value":306}," идеально, review за один сеанс",{"type":32,"tag":78,"props":308,"children":309},{},[310,315],{"type":32,"tag":82,"props":311,"children":312},{},[313],{"type":37,"value":314},"Средний (100-250 строк):",{"type":37,"value":316}," приемлемо, review за 2 сеанса",{"type":32,"tag":78,"props":318,"children":319},{},[320,325],{"type":32,"tag":82,"props":321,"children":322},{},[323],{"type":37,"value":324},"Большой (250-400 строк):",{"type":37,"value":326}," рекомендация разделить, нужно обоснование",{"type":32,"tag":78,"props":328,"children":329},{},[330,335],{"type":32,"tag":82,"props":331,"children":332},{},[333],{"type":37,"value":334},"Очень большой (>400 строк):",{"type":37,"value":336}," автоматический отказ, требуется рефакторинг",{"type":32,"tag":33,"props":338,"children":339},{},[340],{"type":37,"value":341},"Для создания культуры «маленьких PR» работают такие тактики:",{"type":32,"tag":343,"props":344,"children":345},"ol",{},[346,356,366],{"type":32,"tag":78,"props":347,"children":348},{},[349,354],{"type":32,"tag":82,"props":350,"children":351},{},[352],{"type":37,"value":353},"Feature flagging:",{"type":37,"value":355}," добавляйте новую функцию в production маленькими PR'ами с флагом отключения. Последний PR включает флаг.",{"type":32,"tag":78,"props":357,"children":358},{},[359,364],{"type":32,"tag":82,"props":360,"children":361},{},[362],{"type":37,"value":363},"Stacked PRs:",{"type":37,"value":365}," PR2 можно открыть до merge'а PR1, но база PR2 — это PR1. Линейная зависимость, все куски маленькие.",{"type":32,"tag":78,"props":367,"children":368},{},[369,374],{"type":32,"tag":82,"props":370,"children":371},{},[372],{"type":37,"value":373},"Draft PR:",{"type":37,"value":375}," хотите архитектурное мнение до завершения? Откройте draft. Это не считается review, informal feedback.",{"type":32,"tag":33,"props":377,"children":378},{},[379,384],{"type":32,"tag":82,"props":380,"children":381},{},[382],{"type":37,"value":383},"Comment density:",{"type":37,"value":385}," 2-4 комментария на PR — идеально. 0 комментариев: либо тривиальное изменение, либо reviewer не посмотрел. 8+ комментариев: scope съехал или стандарт неясен.",{"type":32,"tag":40,"props":387,"children":389},{"id":388},"измеримые-метрики-качества-dashboard-reviewа",[390],{"type":37,"value":391},"Измеримые метрики качества: dashboard review'а",{"type":32,"tag":33,"props":393,"children":394},{},[395],{"type":37,"value":396},"Культура review'а управляется данными. В Roibase еженедельный dashboard содержит:",{"type":32,"tag":74,"props":398,"children":399},{},[400,410,420,430],{"type":32,"tag":78,"props":401,"children":402},{},[403,408],{"type":32,"tag":82,"props":404,"children":405},{},[406],{"type":37,"value":407},"Median time-to-review:",{"type":37,"value":409}," среднее значение по команде, видны личные outlier'ы",{"type":32,"tag":78,"props":411,"children":412},{},[413,418],{"type":32,"tag":82,"props":414,"children":415},{},[416],{"type":37,"value":417},"Approval rate first round:",{"type":37,"value":419}," процент одобрения на первый review (цель >60%)",{"type":32,"tag":78,"props":421,"children":422},{},[423,428],{"type":32,"tag":82,"props":424,"children":425},{},[426],{"type":37,"value":427},"Breakdown comment type:",{"type":37,"value":429}," nit-pick (\u003C20%), баги (>30%), архитектурные дискуссии (~50%)",{"type":32,"tag":78,"props":431,"children":432},{},[433,438],{"type":32,"tag":82,"props":434,"children":435},{},[436],{"type":37,"value":437},"Blocked PR count:",{"type":37,"value":439}," PR'ы, ожидающие >24 часов (цель 0)",{"type":32,"tag":33,"props":441,"children":442},{},[443],{"type":37,"value":444},"Этот dashboard лучше собирать не из Linear\u002FJira, а через GitHub API + custom скрипт. Пример:",{"type":32,"tag":446,"props":447,"children":451},"pre",{"code":448,"language":449,"meta":16,"className":450,"style":16},"# Упрощённый пример — в production используйте GitHub GraphQL API\ndef calculate_review_metrics(repo, start_date):\n    prs = repo.get_pulls(state='closed', sort='updated', direction='desc')\n    \n    metrics = {\n        'time_to_first_review': [],\n        'time_to_merge': [],\n        'comment_density': []\n    }\n    \n    for pr in prs:\n        reviews = pr.get_reviews()\n        if reviews.totalCount > 0:\n            first_review = reviews[0].submitted_at\n            time_diff = (first_review - pr.created_at).total_seconds() \u002F 3600\n            metrics['time_to_first_review'].append(time_diff)\n        \n        if pr.merged:\n            merge_time = (pr.merged_at - pr.created_at).total_seconds() \u002F 3600\n            metrics['time_to_merge'].append(merge_time)\n        \n        metrics['comment_density'].append(pr.comments)\n    \n    return {\n        'median_time_to_review': median(metrics['time_to_first_review']),\n        'median_time_to_merge': median(metrics['time_to_merge']),\n        'avg_comment_density': mean(metrics['comment_density'])\n    }\n","python","language-python shiki shiki-themes github-dark",[452],{"type":32,"tag":52,"props":453,"children":454},{"__ignoreMap":16},[455,467,489,566,575,593,607,620,633,642,650,674,692,722,750,788,807,816,829,863,881,889,908,916,929,952,973,996],{"type":32,"tag":456,"props":457,"children":460},"span",{"class":458,"line":459},"line",1,[461],{"type":32,"tag":456,"props":462,"children":464},{"style":463},"--shiki-default:#6A737D",[465],{"type":37,"value":466},"# Упрощённый пример — в production используйте GitHub GraphQL API\n",{"type":32,"tag":456,"props":468,"children":470},{"class":458,"line":469},2,[471,477,483],{"type":32,"tag":456,"props":472,"children":474},{"style":473},"--shiki-default:#F97583",[475],{"type":37,"value":476},"def",{"type":32,"tag":456,"props":478,"children":480},{"style":479},"--shiki-default:#B392F0",[481],{"type":37,"value":482}," calculate_review_metrics",{"type":32,"tag":456,"props":484,"children":486},{"style":485},"--shiki-default:#E1E4E8",[487],{"type":37,"value":488},"(repo, start_date):\n",{"type":32,"tag":456,"props":490,"children":492},{"class":458,"line":491},3,[493,498,503,508,514,518,524,529,534,538,543,547,552,556,561],{"type":32,"tag":456,"props":494,"children":495},{"style":485},[496],{"type":37,"value":497},"    prs ",{"type":32,"tag":456,"props":499,"children":500},{"style":473},[501],{"type":37,"value":502},"=",{"type":32,"tag":456,"props":504,"children":505},{"style":485},[506],{"type":37,"value":507}," repo.get_pulls(",{"type":32,"tag":456,"props":509,"children":511},{"style":510},"--shiki-default:#FFAB70",[512],{"type":37,"value":513},"state",{"type":32,"tag":456,"props":515,"children":516},{"style":473},[517],{"type":37,"value":502},{"type":32,"tag":456,"props":519,"children":521},{"style":520},"--shiki-default:#9ECBFF",[522],{"type":37,"value":523},"'closed'",{"type":32,"tag":456,"props":525,"children":526},{"style":485},[527],{"type":37,"value":528},", ",{"type":32,"tag":456,"props":530,"children":531},{"style":510},[532],{"type":37,"value":533},"sort",{"type":32,"tag":456,"props":535,"children":536},{"style":473},[537],{"type":37,"value":502},{"type":32,"tag":456,"props":539,"children":540},{"style":520},[541],{"type":37,"value":542},"'updated'",{"type":32,"tag":456,"props":544,"children":545},{"style":485},[546],{"type":37,"value":528},{"type":32,"tag":456,"props":548,"children":549},{"style":510},[550],{"type":37,"value":551},"direction",{"type":32,"tag":456,"props":553,"children":554},{"style":473},[555],{"type":37,"value":502},{"type":32,"tag":456,"props":557,"children":558},{"style":520},[559],{"type":37,"value":560},"'desc'",{"type":32,"tag":456,"props":562,"children":563},{"style":485},[564],{"type":37,"value":565},")\n",{"type":32,"tag":456,"props":567,"children":569},{"class":458,"line":568},4,[570],{"type":32,"tag":456,"props":571,"children":572},{"style":485},[573],{"type":37,"value":574},"    \n",{"type":32,"tag":456,"props":576,"children":578},{"class":458,"line":577},5,[579,584,588],{"type":32,"tag":456,"props":580,"children":581},{"style":485},[582],{"type":37,"value":583},"    metrics ",{"type":32,"tag":456,"props":585,"children":586},{"style":473},[587],{"type":37,"value":502},{"type":32,"tag":456,"props":589,"children":590},{"style":485},[591],{"type":37,"value":592}," {\n",{"type":32,"tag":456,"props":594,"children":596},{"class":458,"line":595},6,[597,602],{"type":32,"tag":456,"props":598,"children":599},{"style":520},[600],{"type":37,"value":601},"        'time_to_first_review'",{"type":32,"tag":456,"props":603,"children":604},{"style":485},[605],{"type":37,"value":606},": [],\n",{"type":32,"tag":456,"props":608,"children":610},{"class":458,"line":609},7,[611,616],{"type":32,"tag":456,"props":612,"children":613},{"style":520},[614],{"type":37,"value":615},"        'time_to_merge'",{"type":32,"tag":456,"props":617,"children":618},{"style":485},[619],{"type":37,"value":606},{"type":32,"tag":456,"props":621,"children":622},{"class":458,"line":26},[623,628],{"type":32,"tag":456,"props":624,"children":625},{"style":520},[626],{"type":37,"value":627},"        'comment_density'",{"type":32,"tag":456,"props":629,"children":630},{"style":485},[631],{"type":37,"value":632},": []\n",{"type":32,"tag":456,"props":634,"children":636},{"class":458,"line":635},9,[637],{"type":32,"tag":456,"props":638,"children":639},{"style":485},[640],{"type":37,"value":641},"    }\n",{"type":32,"tag":456,"props":643,"children":645},{"class":458,"line":644},10,[646],{"type":32,"tag":456,"props":647,"children":648},{"style":485},[649],{"type":37,"value":574},{"type":32,"tag":456,"props":651,"children":653},{"class":458,"line":652},11,[654,659,664,669],{"type":32,"tag":456,"props":655,"children":656},{"style":473},[657],{"type":37,"value":658},"    for",{"type":32,"tag":456,"props":660,"children":661},{"style":485},[662],{"type":37,"value":663}," pr ",{"type":32,"tag":456,"props":665,"children":666},{"style":473},[667],{"type":37,"value":668},"in",{"type":32,"tag":456,"props":670,"children":671},{"style":485},[672],{"type":37,"value":673}," prs:\n",{"type":32,"tag":456,"props":675,"children":677},{"class":458,"line":676},12,[678,683,687],{"type":32,"tag":456,"props":679,"children":680},{"style":485},[681],{"type":37,"value":682},"        reviews ",{"type":32,"tag":456,"props":684,"children":685},{"style":473},[686],{"type":37,"value":502},{"type":32,"tag":456,"props":688,"children":689},{"style":485},[690],{"type":37,"value":691}," pr.get_reviews()\n",{"type":32,"tag":456,"props":693,"children":695},{"class":458,"line":694},13,[696,701,706,711,717],{"type":32,"tag":456,"props":697,"children":698},{"style":473},[699],{"type":37,"value":700},"        if",{"type":32,"tag":456,"props":702,"children":703},{"style":485},[704],{"type":37,"value":705}," reviews.totalCount ",{"type":32,"tag":456,"props":707,"children":708},{"style":473},[709],{"type":37,"value":710},">",{"type":32,"tag":456,"props":712,"children":714},{"style":713},"--shiki-default:#79B8FF",[715],{"type":37,"value":716}," 0",{"type":32,"tag":456,"props":718,"children":719},{"style":485},[720],{"type":37,"value":721},":\n",{"type":32,"tag":456,"props":723,"children":725},{"class":458,"line":724},14,[726,731,735,740,745],{"type":32,"tag":456,"props":727,"children":728},{"style":485},[729],{"type":37,"value":730},"            first_review ",{"type":32,"tag":456,"props":732,"children":733},{"style":473},[734],{"type":37,"value":502},{"type":32,"tag":456,"props":736,"children":737},{"style":485},[738],{"type":37,"value":739}," reviews[",{"type":32,"tag":456,"props":741,"children":742},{"style":713},[743],{"type":37,"value":744},"0",{"type":32,"tag":456,"props":746,"children":747},{"style":485},[748],{"type":37,"value":749},"].submitted_at\n",{"type":32,"tag":456,"props":751,"children":753},{"class":458,"line":752},15,[754,759,763,768,773,778,783],{"type":32,"tag":456,"props":755,"children":756},{"style":485},[757],{"type":37,"value":758},"            time_diff ",{"type":32,"tag":456,"props":760,"children":761},{"style":473},[762],{"type":37,"value":502},{"type":32,"tag":456,"props":764,"children":765},{"style":485},[766],{"type":37,"value":767}," (first_review ",{"type":32,"tag":456,"props":769,"children":770},{"style":473},[771],{"type":37,"value":772},"-",{"type":32,"tag":456,"props":774,"children":775},{"style":485},[776],{"type":37,"value":777}," pr.created_at).total_seconds() ",{"type":32,"tag":456,"props":779,"children":780},{"style":473},[781],{"type":37,"value":782},"\u002F",{"type":32,"tag":456,"props":784,"children":785},{"style":713},[786],{"type":37,"value":787}," 3600\n",{"type":32,"tag":456,"props":789,"children":791},{"class":458,"line":790},16,[792,797,802],{"type":32,"tag":456,"props":793,"children":794},{"style":485},[795],{"type":37,"value":796},"            metrics[",{"type":32,"tag":456,"props":798,"children":799},{"style":520},[800],{"type":37,"value":801},"'time_to_first_review'",{"type":32,"tag":456,"props":803,"children":804},{"style":485},[805],{"type":37,"value":806},"].append(time_diff)\n",{"type":32,"tag":456,"props":808,"children":810},{"class":458,"line":809},17,[811],{"type":32,"tag":456,"props":812,"children":813},{"style":485},[814],{"type":37,"value":815},"        \n",{"type":32,"tag":456,"props":817,"children":819},{"class":458,"line":818},18,[820,824],{"type":32,"tag":456,"props":821,"children":822},{"style":473},[823],{"type":37,"value":700},{"type":32,"tag":456,"props":825,"children":826},{"style":485},[827],{"type":37,"value":828}," pr.merged:\n",{"type":32,"tag":456,"props":830,"children":832},{"class":458,"line":831},19,[833,838,842,847,851,855,859],{"type":32,"tag":456,"props":834,"children":835},{"style":485},[836],{"type":37,"value":837},"            merge_time ",{"type":32,"tag":456,"props":839,"children":840},{"style":473},[841],{"type":37,"value":502},{"type":32,"tag":456,"props":843,"children":844},{"style":485},[845],{"type":37,"value":846}," (pr.merged_at ",{"type":32,"tag":456,"props":848,"children":849},{"style":473},[850],{"type":37,"value":772},{"type":32,"tag":456,"props":852,"children":853},{"style":485},[854],{"type":37,"value":777},{"type":32,"tag":456,"props":856,"children":857},{"style":473},[858],{"type":37,"value":782},{"type":32,"tag":456,"props":860,"children":861},{"style":713},[862],{"type":37,"value":787},{"type":32,"tag":456,"props":864,"children":866},{"class":458,"line":865},20,[867,871,876],{"type":32,"tag":456,"props":868,"children":869},{"style":485},[870],{"type":37,"value":796},{"type":32,"tag":456,"props":872,"children":873},{"style":520},[874],{"type":37,"value":875},"'time_to_merge'",{"type":32,"tag":456,"props":877,"children":878},{"style":485},[879],{"type":37,"value":880},"].append(merge_time)\n",{"type":32,"tag":456,"props":882,"children":884},{"class":458,"line":883},21,[885],{"type":32,"tag":456,"props":886,"children":887},{"style":485},[888],{"type":37,"value":815},{"type":32,"tag":456,"props":890,"children":892},{"class":458,"line":891},22,[893,898,903],{"type":32,"tag":456,"props":894,"children":895},{"style":485},[896],{"type":37,"value":897},"        metrics[",{"type":32,"tag":456,"props":899,"children":900},{"style":520},[901],{"type":37,"value":902},"'comment_density'",{"type":32,"tag":456,"props":904,"children":905},{"style":485},[906],{"type":37,"value":907},"].append(pr.comments)\n",{"type":32,"tag":456,"props":909,"children":911},{"class":458,"line":910},23,[912],{"type":32,"tag":456,"props":913,"children":914},{"style":485},[915],{"type":37,"value":574},{"type":32,"tag":456,"props":917,"children":919},{"class":458,"line":918},24,[920,925],{"type":32,"tag":456,"props":921,"children":922},{"style":473},[923],{"type":37,"value":924},"    return",{"type":32,"tag":456,"props":926,"children":927},{"style":485},[928],{"type":37,"value":592},{"type":32,"tag":456,"props":930,"children":932},{"class":458,"line":931},25,[933,938,943,947],{"type":32,"tag":456,"props":934,"children":935},{"style":520},[936],{"type":37,"value":937},"        'median_time_to_review'",{"type":32,"tag":456,"props":939,"children":940},{"style":485},[941],{"type":37,"value":942},": median(metrics[",{"type":32,"tag":456,"props":944,"children":945},{"style":520},[946],{"type":37,"value":801},{"type":32,"tag":456,"props":948,"children":949},{"style":485},[950],{"type":37,"value":951},"]),\n",{"type":32,"tag":456,"props":953,"children":955},{"class":458,"line":954},26,[956,961,965,969],{"type":32,"tag":456,"props":957,"children":958},{"style":520},[959],{"type":37,"value":960},"        'median_time_to_merge'",{"type":32,"tag":456,"props":962,"children":963},{"style":485},[964],{"type":37,"value":942},{"type":32,"tag":456,"props":966,"children":967},{"style":520},[968],{"type":37,"value":875},{"type":32,"tag":456,"props":970,"children":971},{"style":485},[972],{"type":37,"value":951},{"type":32,"tag":456,"props":974,"children":976},{"class":458,"line":975},27,[977,982,987,991],{"type":32,"tag":456,"props":978,"children":979},{"style":520},[980],{"type":37,"value":981},"        'avg_comment_density'",{"type":32,"tag":456,"props":983,"children":984},{"style":485},[985],{"type":37,"value":986},": mean(metrics[",{"type":32,"tag":456,"props":988,"children":989},{"style":520},[990],{"type":37,"value":902},{"type":32,"tag":456,"props":992,"children":993},{"style":485},[994],{"type":37,"value":995},"])\n",{"type":32,"tag":456,"props":997,"children":999},{"class":458,"line":998},28,[1000],{"type":32,"tag":456,"props":1001,"children":1002},{"style":485},[1003],{"type":37,"value":641},{"type":32,"tag":33,"props":1005,"children":1006},{},[1007],{"type":37,"value":1008},"Dashboard открывают раз в 2 недели на retrospective. Вопрос типа «в этом спринте median time-to-review 5,2 часа, цель 4 часа — где упирались?» — это не личное, это системный анализ.",{"type":32,"tag":40,"props":1010,"children":1012},{"id":1011},"границы-автоматизации-как-культурное-правило",[1013],{"type":37,"value":1014},"Границы автоматизации как культурное правило",{"type":32,"tag":33,"props":1016,"children":1017},{},[1018],{"type":37,"value":1019},"Linter и CI не решают всё. Архитектурные решения, trade-off'ы, бизнес-логика — всё это ещё требует человека. Но гарантируйте: автоматизация ловит «простые ошибки» заранее, человеческое время идёт на «сложное мышление».",{"type":32,"tag":33,"props":1021,"children":1022},{},[1023],{"type":32,"tag":82,"props":1024,"children":1025},{},[1026],{"type":37,"value":1027},"Что передать автоматизации:",{"type":32,"tag":74,"props":1029,"children":1030},{},[1031,1036,1041,1046],{"type":32,"tag":78,"props":1032,"children":1033},{},[1034],{"type":37,"value":1035},"Проверка форматирования (Prettier, ESLint)",{"type":32,"tag":78,"props":1037,"children":1038},{},[1039],{"type":37,"value":1040},"Type safety (TypeScript strict mode)",{"type":32,"tag":78,"props":1042,"children":1043},{},[1044],{"type":37,"value":1045},"Покрытие тестами (Jest threshold)",{"type":32,"tag":78,"props":1047,"children":1048},{},[1049],{"type":37,"value":1050},"Сканирование безопасности (Snyk, Dependabot)",{"type":32,"tag":33,"props":1052,"children":1053},{},[1054],{"type":32,"tag":82,"props":1055,"children":1056},{},[1057],{"type":37,"value":1058},"Что оставить человеку:",{"type":32,"tag":74,"props":1060,"children":1061},{},[1062,1067,1072,1077],{"type":32,"tag":78,"props":1063,"children":1064},{},[1065],{"type":37,"value":1066},"Согласованность API дизайна",{"type":32,"tag":78,"props":1068,"children":1069},{},[1070],{"type":37,"value":1071},"Решения по оптимизации производительности",{"type":32,"tag":78,"props":1073,"children":1074},{},[1075],{"type":37,"value":1076},"Анализ impact на user flow",{"type":32,"tag":78,"props":1078,"children":1079},{},[1080],{"type":37,"value":1081},"Принятие\u002Fотклонение технического долга",{"type":32,"tag":33,"props":1083,"children":1084},{},[1085],{"type":37,"value":1086},"В команде нормально, когда «linter pass, но architecture review fail». Но ситуация «linter fail и PR открыт» — это системная ошибка, нужен pre-commit hook.",{"type":32,"tag":40,"props":1088,"children":1090},{"id":1089},"тон-и-язык-комментариев-в-code-review",[1091],{"type":37,"value":1092},"Тон и язык комментариев в code review",{"type":32,"tag":33,"props":1094,"children":1095},{},[1096],{"type":37,"value":1097},"Даже с измеримыми правилами люди пишут комментарии. Стандартизуйте и их тон. В Roibase используется такой шаблон:",{"type":32,"tag":33,"props":1099,"children":1100},{},[1101],{"type":32,"tag":82,"props":1102,"children":1103},{},[1104],{"type":37,"value":1105},"Шаблон конструктивного комментария:",{"type":32,"tag":446,"props":1107,"children":1109},{"code":1108},"[Категория] Наблюдение\nОбоснование: ...\nПредложение: ... (необязательно)\nПриоритет: blocking \u002F non-blocking\n",[1110],{"type":32,"tag":52,"props":1111,"children":1112},{"__ignoreMap":16},[1113],{"type":37,"value":1108},{"type":32,"tag":33,"props":1115,"children":1116},{},[1117],{"type":37,"value":1118},"Пример:",{"type":32,"tag":446,"props":1120,"children":1122},{"code":1121},"[Performance] Array.find() вызывается в loop (строки 45-52)\nОбоснование: O(n²) сложность, для array'а >1000 элементов даёт 300ms delay\nПредложение: Преобразовать в Map lookup до loop\nПриоритет: blocking\n",[1123],{"type":32,"tag":52,"props":1124,"children":1125},{"__ignoreMap":16},[1126],{"type":37,"value":1121},{"type":32,"tag":33,"props":1128,"children":1129},{},[1130],{"type":37,"value":1131},"Этот формат вместо «твой код плохой» говорит «этот код медленнеет в сценарии Х». Без персонализации, фокус на поведение.",{"type":32,"tag":33,"props":1133,"children":1134},{},[1135,1140],{"type":32,"tag":82,"props":1136,"children":1137},{},[1138],{"type":37,"value":1139},"Non-blocking комментарий:",{"type":37,"value":1141}," «Это работает, но в сценарии Y могут быть проблемы Z.» Merge не блокирует, идёт в список технического долга.",{"type":32,"tag":33,"props":1143,"children":1144},{},[1145,1150],{"type":32,"tag":82,"props":1146,"children":1147},{},[1148],{"type":37,"value":1149},"Blocking комментарий:",{"type":37,"value":1151}," «Security issue — пользовательский ввод не sanitize.» Merge невозможен, исправление обязательно.",{"type":32,"tag":33,"props":1153,"children":1154},{},[1155],{"type":37,"value":1156},"Без tag приоритета default — non-blocking. Если есть blocking — PR не pass'ит, без blocking — pass'ит.",{"type":32,"tag":40,"props":1158,"children":1160},{"id":1159},"закрытие-от-личных-конфликтов-к-численной-системе",[1161],{"type":37,"value":1162},"Закрытие: от личных конфликтов к численной системе",{"type":32,"tag":33,"props":1164,"children":1165},{},[1166],{"type":37,"value":1167},"Культура code review не строится на «добрых намерениях». Даже хорошо намеренные команды падают в субъективные дебаты из-за неясных стандартов. Решение: определите метрики (time-to-review, comment density, PR size), enforcement'нуйте через автоматизацию, отслеживайте на dashboard'е. Эта дисциплина экономит время разработчика, убирает личный произвол reviewer'а, увеличивает velocity команды. 8+ лет опыта руководства показывает: качество, которое не измеряют, не улучшается — измеряйте, оптимизируйте, повторяйте.",{"type":32,"tag":1169,"props":1170,"children":1171},"style",{},[1172],{"type":37,"value":1173},"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":16,"searchDepth":491,"depth":491,"links":1175},[1176,1177,1180,1181,1182,1183,1184],{"id":42,"depth":469,"text":45},{"id":132,"depth":469,"text":135,"children":1178},[1179],{"id":266,"depth":491,"text":269},{"id":277,"depth":469,"text":280},{"id":388,"depth":469,"text":391},{"id":1011,"depth":469,"text":1014},{"id":1089,"depth":469,"text":1092},{"id":1159,"depth":469,"text":1162},"markdown","content:ru:lifestyle:code-review-kulturi-olculebilir-kalite.md","content","ru\u002Flifestyle\u002Fcode-review-kulturi-olculebilir-kalite.md","ru\u002Flifestyle\u002Fcode-review-kulturi-olculebilir-kalite","md",1778709808798]