355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Alan Carter » The Programmers' Stone (Программистский камень) » Текст книги (страница 4)
The Programmers' Stone (Программистский камень)
  • Текст добавлен: 26 сентября 2016, 13:28

Текст книги "The Programmers' Stone (Программистский камень)"


Автор книги: Alan Carter


Соавторы: Colston Sanger
сообщить о нарушении

Текущая страница: 4 (всего у книги 13 страниц)

Мы должны смотреть на исходный код, который мы создаем, не как на конечный продукт более интересного процесса, а как на явление со своими собственными правилами. Он должен хорошо выглядеть, если его повесить на стену. Затраты на то, чтобы действительно взглянуть на наш код и изучить закономерности геометрических узоров черного и белого, узоры в синтаксисе и узоры в проблемной области, сами по себе не так уж велики, но иногда позволяют буквально увидеть ошибки с расстояния шести футов.

С точки зрения всей этой литературной критики, что можно сказать о религиозных войнах? Конечно, часто они происходят развлечения ради, и мы не хотим препятствовать удовольствию от нелепых особенностей любимых инструментов и технологий наших друзей! Но иногда разумные программисты впадают в изматывающие и снижающие производительность перебранки, которые просто идут по кругу. Мы забываем, что строго между собой мы можем использовать структурные аргументы. Когда возникает нежелательная религиозная война, задайте следующие вопросы:

Какова глобальная позиция, включающая оба специальных случая?Имеется ли различие намерений между позициями?В чем состоит общая цель?

Например, вы оцениваете возможности мощной интегрированной среды. Вы используете Emacs на работе и доработали его, чтобы он управлял вашей кофеваркой. Я работаю на многих машинах, и, как это ни эксцентрично, я знаю, что на них всегда есть vi. Мы устанавливаем Emacs на новых машинах и обучаем vi новичков. Ваш LISP, конечно, sucks.

Это соотнесение возможностей и целей часто дает великолепное примирение мнений у квалифицированных людей. Возможность прийти к соглашению о наилучших идиомах, чтобы сделать работу в хорошо понимаемой среде, не означает, что все должны изменить свое мнение – они просто соглашаются. В противоположность популярному мнению, часто это правильный ответ. Разговаривая с квалифицированным человеком об идиомах новой среды, можно научится очень многому очень быстро, тогда как использование идиом из старой среды в новой приведет к постоянным стычкам.

Атомы познания

В любой задаче, требующей понимания, мы всегда будем находить по крайней мере один "атом познания". Атом познания – это часть проблемы, которая может быть адекватно рассмотрена только при условии загрузки ее элементов, черт, знаков и т.п. в сознание отдельного картостроителя и получения наилучшего возможного результата. Слово «адекватно» здесь очень важно – существует целый букет проблем, которые, если бы имелись неограниченные ресурсы, могли быть разрешены играючи, но нужно очень хорошо подумать, чтобы решить их для реальных условий. Например, любая толпа идиотов могла бы справиться с задачей смены декораций, которая возникает во время большого музыкального шоу, если на это дано несколько недель. Но сделать то же самое за время, пока конферансье что-то меланхолично бубнит в свете единственного прожектора, требует гениальности.

Опытные планировщики проектов обнаружили, что распознавание и управление атомами познания в рамках проекта – решающий начальный шаг. Сначала мы должны распознать атомы познания. Существует взаимосвязь между архитектурой системы и атомами познания, которые она содержит – архитектор должен применить интуицию и опыт для выявления решаемых, но еще не решенных проблем. Проблемы, которые, как надеется архитектор, могут быть решены при разработке, будут влиять на дизайн, поскольку никто не хочет создавать архитектуру, которую нельзя реализовать!

Поэтому архитектор может очертить границы атомов познания вокруг проблемы. Например, в системах добычи знаний (data mining system), практические комбинаторные проблемы могут быть сконцентрированы в базе данных, либо на более высоком уровне прикладной логики. Правильная идентификация атомов познания будет управлять как архитектурой, так и рабочими пакетами, порученными отдельным членам команды. Каждый атом должен быть передан одному человеку или подгруппе для решения, но они могут обнаружить, что работают над более чем одной частью системы, чтобы разрешить свою проблему. Поэтому части должны быть хорошо разбиты на уровни, так что модули не сталкиваются в бестолковых сражениях. Идентификация атомов обычно требует учета баланса времени, пространства, связи, риска, возможностей команды, переносимости, времени разработки, и все это должно быть проделано при наличии атомов, разрешимость которых неочевидна. Поэтому архитектор должен суметь увидеть ключевую проблему и выразить, по крайней мере в своей собственной голове, природу условий компромиссов. Вполне возможно распознать набор очевидных компромиссов, о котором очень трудно рассказать другому, не обладающему, как картостроитель, способностью видеть структуру. Превращение мысленной модели в последовательность [действий] всегда тяжело, поскольку мы не думаем на языке технических бумаг, которые загружаем по ftp.

Во время идентификации атомов познания очень важно избежать специфических заблуждений, которые повторяются раз за разом. Часто возможно раздробить атом на более мелкие части не сильно задумываясь, и таким образом достигнуть этапа кодирования без больших усилий. Но когда дело доходит до реализации, все ввергается в хаос. Реальные проблемы никуда не деваются, они просто оборачиваются уродливыми API подсистем, проблемами производительности, ненадежностью и т.д. Границы атомов познания сжимаются все сильнее и сильнее, до ... Хоп! Они вновь возникают на уровне всей системы! Идеология упрощающего пошагового уточнения без регулярной сверки с действительностью и попыток найти логические ошибки в проекте ответственна за великое множество трагедий, включая потерю большей части отведенного на проект времени на попытки выполнять текущую проектную работу с чистосердечной неформальной желчностью, за которой следуют отчаянные попытки залатать дыры в программе.

Определение границ атома познания может происходить циклически, а квалифицированный архитектор укажет для них правильное место, где верхний уровень, где нижний, а что посередине. На начальных стадиях это может быть огромный, единый атом познания, который нужно передать в руки ответственного работника и сказать: "Попробуй разобрать эту мешанину, пожалуйста!"

По определению мы не знаем, каков наилучший подход к атому познания. Если бы мы знали, то он не был бы атомом. Из этого следует, что это не может планироваться на основе диаграмм планирования проектов (диаграмм Ганта) в терминах подцелей. Это должно быть одной задачей, а о длительности можно только догадываться. Опытные картостроители поднаторели в догадках, но они не могут объяснить, почему проблема тянет на два дня, неделю, полгода. Поэтому у того, кто дал наилучший прогноз, очень мало аргументов в его защиту. Боязнь последующих объяснений – важный фактор, который часто отбивает у картостроителей охоту проявлять свои интуитивные способности и выдавать необходимые для планирования проекта цифры.

В результате расщепления атома познания работник обычно может выложить очень детальный набор описаний задач (целей), основанный на твердом понимании того, что должно быть сделано. Таким образом, во многих проектах следует поправить диаграммы Ганта, добавив туда расщепление атомов познания. Мы предполагаем, что большая часть проектов, пытающихся распланировать по Ганту все по дням, демонстрирует всеобъемлющую линейную модель производства. Программисты, работающие по таким диаграммам Ганта, не могут получить выгод из разумного управления атомами познания. Вместо того, чтобы повернуть свой разум к решаемой проблеме, они будут доказывать, что они хорошие работники, под "прессом", как будто унижая их можно заставить думать более ясно. Это грозит стрессом и снижает производительность.

Плато качества

Когда принята стратегия формирования мысленной карты проблемной области и попыток упростить ее, обычно сталкиваются с проблемой определения момента окончания работы над картой. Эта проблема возникает на каждом уровне проектирования. Сверхъестественно, но почти всегда есть глубокое решение, которое значительно проще всех остальных и очевидно минимальное. (Есть много способов это выразить, но потом этот вывод станет очевидным.) Хотя проповеди типа: «Ты узнаешь это, когда увидишь!» – несомненная истина, но они не говорят, куда посмотреть.

Единственный честный аргумент, который мы можем здесь предложить – это обещание, что это действительно произойдет. И хотя это ничего не доказывает, все что мы можем сделать – это показать работающий пример приведения в минимальное состояние. Но это работает – спросите любого, кто пытался.

В качестве примера возьмем код из прекрасной книги Джеффри Рихтера (Jeffrey Richter's Advanced Windows). Эта книга – полезное чтение для любого, кто пытается писать программы для Win32 API (Application Programming Interface) (поскольку иначе у вас не появится мысленная карта семантики системы).

Рихтер очень четко раскладывает по полочкам вопросы использования Win32, но даже в его примерах (и, в частности, как результат соглашений, которым он следует) появляется сложность, которую мы попробуем убрать. На странице 319 имеется функция SecondThread() Мы просто посмотрим на эту функцию, опустив остальную программу и некоторые глобальные определения:

DWORD WINAPI SecondThread (LPVOID lpwThreadParm) {

BOOL fDone = FALSE;

DWORD dw;

while (!fDone) {     // Wait forever for the mutex to become signaled.

  dw = WaitForSingleObject(g_hMutex, INFINITE);

  if (dw == WAIT_OBJECT_0) { // Mutex became signalled.

   if (g_nIndex >= MAX_TIMES) {

       fDone = TRUE;

    } else {

       g_nIndex++;

      g_dwTimes[g_nIndex – 1] = GetTickCount():

    } // Release the mutex.

  ReleaseMutex(g_hMutex);

} else { // The mutex was abandoned.

break; // Exit the while loop.

}

}

return(0);

}

Для начала просто упростим стиль скобок, уберем пробел между ключевым словом и открывающей скобкой, а также многословный комментарий к ReleaseMutex. Мы в курсе, что идет религиозная война между последователями Кернигана и Ритчи (K&R) и последователями Вирта (Wirth) по поводу стиля скобок, но симметрия обрамления блока действительно позволяет лучше увидеть некоторые вещи. Дополнительная строка, которая при этом появляется, даст выигрыш чуть позднее – следуйте за нами!

DWORD WINAPI SecondThread(LPVOID lpwThreadParm) { BOOL fDone = FALSE; DWORD dw; while(!fDone) { // Wait forever for the mutex to become signaled. dw = WaitForSingleObject(g_hMutex, INFINITE); if(dw == WAIT_OBJECT_0) { // Mutex became signalled. if(g_nIndex >= MAX_TIMES) { fDone = TRUE; } else { g_nIndex++; g_dwTimes[g_nIndex – 1] = GetTickCount(): } ReleaseMutex(g_hMutex); } else { // The mutex was abandoned. break; // Exit the while loop. } } return(0); }

Очень легко можно избавиться от одной локальной переменной: dw присваивают значение, а в следующей операции тестируют. Инвертирование смысла проверки помогает локализовать ссылку (проверка, затем изменение g_nIndex). А пока мы здесь, нет смысла инкрементировать g_nIndex просто для того, чтобы вычесть 1 из текущего значения в следующей операции! Мы уже использовали постфиксную форму оператора инкремента языка Cи, который как раз для этого и предназначен.

DWORD WINAPI SecondThread (LPVOID lpwThreadParm) { BOOL fDone = FALSE; while (!fDone) { // Wait forever for the mutex to become signaled. if (WaitForSingleObject(g_hMutex, INFINITE)==WAIT_OBJECT_0) { // Mutex became signalled. if (g_nIndex < MAX_TIMES) { g_dwTimes[g_nIndex++] = GetTickCount(); } else { fDone = TRUE; } ReleaseMutex(g_hMutex); } else { // The mutex was abandoned. break;// Exit the while loop. } } return(0); }

Прерывание цикла (break) зависит только от результата WaitForSingleObject, поэтому естественно переместить проверку в управляющее выражение, избавляясь от прерывания цикла и одного уровня вложенности:

DWORD WINAPI SecondThread (LPVOID lpwThreadParm) { BOOL fDone = FALSE; while (!fDone && WaitForSingleObject(g_hMutex, INFINITE)==WAIT_OBJECT_0) { // Mutex became signalled. if (g_nIndex < MAX_TIMES) { g_dwTimes[g_nIndex++] = GetTickCount(); } else { fDone = TRUE; } ReleaseMutex(g_hMutex); } return(0); }

Теперь просто сожмем... Мы знаем – многие стандарты кодирования говорят, что мы всегда должны ставить фигурные скобки, поскольку иногда у глупых людей получается нечитаемая мешанина, но посмотрите, что получается, когда мы пренебрегаем этим правилом и концентрируемся на повышении читаемости кода.

DWORD WINAPI SecondThread (LPVOID lpwThreadParm) { BOOL fDone = FALSE; while (!fDone && WaitForSingleObject(g_hMutex, INFINITE)==WAIT_OBJECT_0) { if (g_nIndex < MAX_TIMES) g_dwTimes[g_nIndex++] = GetTickCount(); else fDone = TRUE; ReleaseMutex(g_hMutex); } return(0); }

Теперь немного настоящей ереси. Черт возьми, в момент когда мы покончим с этой полной безответственностью, результат окажется совершенно неочевидным. (Здравый смысл поможет сделать лучше, чем правила.)

Ересь в том, что если мы знаем, для чего наши переменные, то мы знаем их типы. Если мы не знаем, для чего предназначена переменная, знание ее типа мало поможет. В любом случае, компилятор все равно сделает проверку типов. Поэтому избавимся от венгерской записи, а заодно и от переопределений типов, которые просто определены ( #define ), но не для нас. Сокрытие разыменования используя typedef – другое бесцельное упражнение, поскольку хотя и позволяет выполнить некоторую инкапсуляцию валюты, этого совершенно недостаточно, чтобы избавиться от беспокойства по этому поводу, поэтому аккуратные программисты вынуждены держать настоящие типы в голове. Поддержка концепции дальних указателей в именах переменных для 32 битного API с плоской адресацией – тоже довольно глупое занятие.

DWORD SecondThread (void *ThreadParm) { BOOL done = FALSE; while (!done && WaitForSingleObject(Mutex, INFINITE)==WAIT_OBJECT_0) { if (Index < MAX_TIMES) Times[Index++] = GetTickCount(); else done = TRUE; ReleaseMutex(Mutex); } return(0); }

Теперь смотрите. Мы достигнем Плато Качества...

DWORD SecondThread(void *ThreadParm) { while(Index < MAX_TIMES && WaitForSingleObject(Mutex, INFINITE) == WAIT_OBJECT_0) { if (Index < MAX_TIMES) Times[Index++] = GetTickCount(): ReleaseMutex(Mutex); } return(0); }

Одиннадцать строк против 26. На один уровень меньшая вложенность, но структура полностью прозрачна. Две локальных переменных исчезли. Нет блоков. Совсем нет вложенных else. Меньше мест, где могут скрываться ошибки.

(Если вы еще не программировали используя потоки (threads), то повторная проверка значения Index внутри тела цикла кажется грубой и ненужной. Если же программировали, то повторная проверка естественна и очевидна. Это очень важно: пока текущий поток приостановлен в WaitForSingleObject, другой поток скорее всего будет активен и изменит значение. То, что для вас очевидно, зависит от вашего опыта: еще одна мораль из этого примера – рассмотрения только структуры куска кода недостаточно.)

Наконец, текст делает совершенно ясным, что разные потоки выполняют функции в разных контекстах. Поэтому совершенно не нужно определять функцию с именем FirstThread(), в точности такую же, как SecondThread(), и вызывать их так:

hThreads[0] = CreateThread(..., FirstThread, ...); hThreads[1] = CreateThread(..., SecondThread, ...);

Когда можно просто

hThreads[0] = CreateThread(..., TheThread, ...); hThreads[1] = CreateThread(..., TheThread, ...);

Почти треть этого примера получена клонированием! Если мы обнаружим ошибку в одной реализации, нам нужно будет не забыть исправить аналогичные ошибки везде. Зачем беспокоиться, когда можно просто слить их в одну. Это хороший способ, когда поджимают сроки.

Знание, а не число строк кода (KLOCS)

Программисты дороги. Результаты их работы должны быть собраны и использованы к выгоде их организации. Проблема в том, что традиционный способ паковщика подбить результаты – подсчитать то, что они могут увидеть. Результаты изучения проблемы командой программистов, пришедшей к пониманию и проверившей это понимание, получив надежный код, выражаются не в числе строк кода (KLOCS), которые они набрали во время изучения. Результаты в окончательном понимании, к которому они пришли, когда закончили.

Причина, по которой важно оценивать результат таким образом, в том, что понимание показывает гораздо более простой способ реализации, чем тот, с которого команда начинала. Классическое поле сражения картостроителей с паковщиками в программировании состоит в том, что картостроители видят, что с тем, что они узнали, повторная реализация может быть сделана быстрее и не будет страдать от проблем сопровождения, вырисовывающихся в существующем коде. Паковщики видят, что картостроители безумствуют, пытаясь разгромить всю их работу (как будто нет резервной копии) и повторить работу нескольких последних месяцев, которые были ужасны, поскольку они, очевидно, не знали, что они делали (они хранят изменяющиеся вещи). Паковщики настраивают одного из своих защитников остановить картостроителей, и организация вынуждена забыть о достигнутом понимании, которое не может быть использовано в контексте существующего кода.

Разумная организация хочет максимального понимания и минимального размера кода, которого только можно достигнуть. Организация, увязшая в модели производства разбухающего кода не учитывает понимание, а подсчитывает свои активы в растущих грудах кода.

Хорошая композиция и экспоненциальный рост продуктивности

Определение хорошей композиции, которое часто используют в художественных школах, звучит так: «Если убрать или изменить любой элемент, то изменится и целое». Вероятно, это морской пейзаж, с маяком, задающим с одной стороны сильную вертикаль, приковывающую взгляд и позиционирующуюся относительно волн позади. Ситуация с маяком (и волнами) – то, что мы распознаем, и тут живопись показывает свою мощь. Если бы вместо величественного маяка стояло приземистое здание, картина говорила бы о чем-то другом. Если бы поверхность воды была гладкой или там резвились купальщики, то картина несла бы еще какие-то сообщения.

Дело в том, что вокруг не должно быть ничего, что не имело бы ясно означенной цели по отношению к другим элементам композиции. Художнику нужно сохранять контроль за сообщением, и если картина содержит случайные предметы, то они будут вызывать в уме зрителя непредсказуемые ассоциации и искажать отношения между важными элементами.

Логики при проверке наборов аксиом сталкиваются с той же самой проблемой. Для этого у них есть гораздо более точный термин, но он происходит просто из компактных формальных структур, в рамках которых делаются наблюдения и доказываются теоремы. Они говорят, что набор аксиом должен быть "необходимым и достаточным". Необходимый и достаточный набор позволяет ясно увидеть «природу» рассматриваемого «мира». Это позволяет удостовериться, что обнаруженные следствия – это истинные следствия исследуемой области, а не какие-то произвольные предположения.

Ни в одной из этих сфер деятельности не нужно напоминать людям о важности сохранения вещей настолько малыми, насколько это возможно, в противоположность программированию. К сожалению, практическая полезность нашего искусства означает, что люди часто стремятся как можно быстрее увидеть новую функциональность, которую мы пытаемся создать. Будучи создана, эта функциональность становится частью фона, и каждый из нас, от корпораций до отдельных людей, становится заложником наших собственных унаследованных систем.

И хотя это может выглядеть как вечная безысходность (неизбежность) Закона Программиста, здесь появляются люди, разрывающие порочный круг деградации, и мы опишем, как это делать с этой точки зрения на программирование как на творческий процесс.

Фундаментальная трудность в сохранении контроля над унаследованными структурами, будь то артефакты стратегии доставки потребителю, возникшие из спецификаций с фиксированной стоимостью амортизации, либо древняя система индексирования CODASYL, которую требуют воссоздать в объектной базе данных – это время. Иногда это выражается как "стоимость", но время редко имеет цену. Это крайние сроки (deadlines). Нет другого способа избежать крайних сроков, кроме как крикнуть "Волк!". Это реалии коммерции, которыми мы не управляем. Все в порядке – мы просто думаем об этом реалистично и скорее учитываем это в своей работе, а не используем для оправдания плохого качества продуктов.

Первая точка приложения усилий против крайних сроков – осознание того, что работа спорится в чистой среде без странных флагов у функций, без противоречивых соглашений о вызовах, без многочисленных соглашений об именовании и прочего барахла. Дни после чистки весомее дней до нее. Поэтому сделайте чистку в самом начале, когда каждый может видеть перед собой большой проект, это время окупится позднее. Вы почти всегда должны делать чистку – код, который большинство организаций помещают в репозитарии, обычно первый кандидат. И не важно, что он прошел все этапы тестирования. Делайте чистку, проверку на старость и даже не обсуждайте сроки, пока не увидите порядка.

Следует сделать предупреждение – будьте реалистами насчет того, сколько времени займет чистка. Чем запутаннее клубок, тем больший эффект даст чистка, но тем больше риск, что у вас не будет времени на его распутывание и выполнение работы. Часто полезно задать вопрос: "Насколько сложно поведение этого предмета как черного ящика?" Если ответ: "Не очень!", то вы знаете, что по мере последовательного вычесывания сложности, он ужмется до чего-то простого, даже если вам вообще не видно, куда идти.

Вторая точка приложения усилий исходит из экспоненциального уменьшения сложности программы. Если у вас более ясный алгоритм, минимальная реализация будет проще. Чем меньше у вас кода, тем легче увидеть структуру кода, и уменьшается шанс появления искажающих концепцию ошибок. В то же время, меньшее количество кода означает и меньше возможностей для синтаксических ошибок, описок в именах переменных и т.д. Меньше ошибок – меньше затраты времени, меньше затраты времени – меньше тестирования. Это не займет много времени в любой команде из более полудюжины человек для большей части их работы, чтобы опуститься до нанесения увечий взаимного перепатчения, когда доступ к репозитарию станет узким местом. Позволить пропустить вещи через такой процесс на более поздних стадиях – заложить бомбу с часовым механизмом, которая рванет тогда, когда уже поздно что-то делать. С другой стороны, безумие отбраковки (выбрасывания лишнего) посреди такой ситуации может восстановить спокойствие в оставшиеся дни.

Третья точка приложения усилий – "дела скунса" (skunkworks), название пошло от названия местечка Skunkworks, основанного корпорацией Lockheed Martin вдали от корпоративного центра, «потому что скунс». [Насколько я слышал, скунс – это такая вонючка, что даже кошек выворачивает – С.К.] Эта страшная технология может быть использована исключительно инициативными командами по секрету на рождественской вечеринке, либо спровоцирована просвещенным руководством. Как повелось во всей этой работе, мы проясним, почему «дела скунса» срабатывают.

В такой деятельности индустриальной эры, как строительство, имеются физические объекты (кирпичи), с которыми трудно управляться. Вместо того, чтобы нагромоздить кирпичи стопкой, чтобы увидеть, сколько их понадобится для строительства дома, мы подсчитываем их. Эта абстракция от физического к информационному дает нам сверхъестественные способности в обращении с кирпичами. В конце концов у нас становится так много чисел, говорящих о поставках, транспортировке и потребностях, что мы вынуждены организовать наши числа в структуры, чтобы работать с ними. Мы используем электронные таблицы, а абстракция от информационного к концептуальному вновь дает нам сверхъестественные способности.

Выполняя такую работу над информацией, как программирование, мы не начинаем с физического и получаем явное преимущество, когда переходим к информационному. Мы начинаем с информационных требований, списков по пунктам и т.п., и нам приходится обрабатывать их с помощью информационных инструментов. Нам приходится делать это из хороших соображений, таких как информационные контракты с заказчиками, информационные соглашения на собраниях с включенными в наш процесс коллегами. Иногда также мы делаем это из таких плохих соображений, как слишком подробные инструкции информационных технологий по преобразованию кирпичей в область информации, как измерение производительности с помощью строк кода (KLOCS).

Беда в том, что при нормальной работе у нас нет рычагов. Информационное содержимое нескольких минут совещания может быть больше информационного содержимого требований, которые на нем обсуждаются! Как деятельность, выполняемая людьми, собрания не способствуют достижению цели! Мы выигрываем только потому, что мы можем продать наше новое знание много раз, или потому, что в соединении с другими знаниями оно дает существенно больший вклад в процесс.

Это лишает возможности использовать понимание для увеличения власти над информацией. "Дела скунса" иногда выглядят как отказ от процесса в интересах творчества. Ничто не может быть дальше от истины. Необходима высокая пропорция опытных людей, чтобы исключить этот эффект, поскольку, чтобы что-то завершить, они должны положить в основу высокоинформативные динамические индивидуальные процессы. То, от чего отказываются – это понимание, содержащееся в изнурительном процессе, ради понимания, которым владеют опытные люди. Отсюда проистекает предусловие для "дел скунса". Отказываясь от детализованного процесса, принимают, что риск неизбежен, а потеря личной безопасности компенсируется простыми хорошо определенными целями. Каждый должен осознать, что "дела скунса" могут потерпеть неудачу, надежды могут не оправдать ожиданий, а могут быть соображения, по которым все вернется к традиционным методам управления. Но когда они срабатывают, то срабатывают великолепно!

Все успешные начинания – "дела скунса". Поэтому есть неудачные начинания. Усилия "дел скунса" могут сменить большой риск потери управляемости [из-за разбухания проекта – С.К.] на малый риск "пионерства" [в смысле быть первым – С.К.]. В таких ситуациях это может оказаться эффективным средством управления риском.


    Ваша оценка произведения:

Популярные книги за неделю