[БЕЗ_ЗВУКА] Завершая разговор о тестировании и отладке программ, давайте вернемся в самое-самое начало. Мы с вами еще в первом курсе «Белый пояс по C++» рассматривали алгоритм решения задачи из нашего курса. И суммарно с тем, что мы говорили и в первом курсе, что мы говорили сейчас про юнит-тестирование, у нас получился вот такой алгоритм. Мы с вами говорили, что если мы написали решение, отправили его в тестирующую систему, и оно не прошло, оно не было ею принято, то нужно это решение протестировать: нужно придумать тесты на решение задачи целиком, протестировать крайние случаи, и тогда, возможно, найдете у себя ошибку. Дальше мы говорили уже в этом, во втором, курсе, что если у вас кончились идеи, вы не можете придумать тесты для вашего решения целиком, то вам нужно выполнить докомпозицию вашего решения и покрыть каждый блок вашей программы unit-тестами. И тогда с высочайшей вероятностью вы в своей программе ошибку найдете, исправите и успешно сдадите программу, сдадите решение в тестирующую систему. Вот такой алгоритм решения задачи программирования у нас, в общем-то, на данный момент есть. Но я считаю, что его вот в этом виде придерживаться не надо. Я вас всячески призываю выполнять декомпозицию программы, не только когда у вас не получилось найти тесты для решения целиком. Я вам предлагаю выполнять декомпозицию вашей задачи сразу, то есть вот делать вот так, чтобы блок «Написание решения» шел после блока «Выполнить декомпозицию». То есть не надо все решение задачи писать функцию main одним монолитным блоком. Лучше сразу продумать, на какие части это решение ваше бьется, и оформить их в виде отдельных функций или классов. Почему это хорошо? Во-первых, потому что когда вы разбили вашу программу на отдельные блоки, то у вас получились маленькие блоки, которые решают какие-то маленькие задачи — задачи меньшего размера, чем та, которая дана вам в нашем курсе. А раз эти маленькие блоки решают маленькие задачи, то их проще реализовать. В них сложнее ошибиться — это маленькая простая задача, значит, легко решить. Дальше. Маленькие блоки проще тестировать. Мы с вами говорили: не можете придумать тесты для решения целиком, разбейте на маленькие блоки, вам проще придумать будет тесты для этих маленьких блоков, которые решают маленькие задачи. Именно поэтому если идей для тестов, для маленького кусочка программы — их обычно достаточно много, и обычно получается придумать все крайние случаи, все какие-то нестандартные варианты входных данных. А раз их проще тестировать, то вы, значит, получите более полный набор тестов. И даже если в этом маленьком блоке допустили ошибку, то набором тестов вы ее найдете достаточно быстро и исправите. Но это, все, что я сейчас говорил, относится к решением наших задач по программированию. Но мы же с вами C++ занимаемся, не чтобы курс пройти, а чтобы потом участвовать в разработке больших проектов, а в больших проектах декомпозиция программы важна гораздо сильнее, чем в домашний заданиях, потому что декомпозиция программы на отдельные блоки, которые решают маленькую понятную задачу, упрощают понимание программы целиком. Кроме того, они упрощают повторное использование кода. Если у вас есть хорошо декомпозированный набор функций или классов, вы просто берете эти классы и используете в другом месте. Вам не нужно ничего копипастить, или выполнять какой-то рефакторинг, или как-то выделять их — у вас уже все готово. Наконец, сама декомпозиция иногда защищает от ошибок. То есть когда вы пишете программу монолитную, вы можете из-за того, что она такая большая и монолитная, допустить какие-то ошибки, которые вы бы не допустили, если б вы выполнили декомпозицию сразу. И вот на этом пункте я бы хотел остановиться поподробнее. Давайте вспомним задачу «Уравнение» из первого курса «Белый пояс по C++». Задача была такая. Нужно было найти все различные действительные корни уравнения Ax² + Bx + C = 0. При этом гарантировалось, что хотя бы один из коэффициентов A, B и C не равен 0. В частности, это уравнение не обязательно квадратное, то есть коэффициент A может быть равен 0, и тогда оно вырождается в линейное. Как бы эту задачу можно было бы решить? Вот давайте переключимся в Eclipse, в котором у меня уже есть одно из возможных решений этой задачи. Вот у нас объявлены какие-то переменные, мы считаем коэффициенты A, B и C, считаем дискриминант, а дальше уходим в большой, такой монолитный блок всяких разных проверок разных случаев, когда у нас A = 0 и C = 0, когда B = 0 и C = 0, еще тут какие-то, значит, варианты рассматриваются, причем это сделано через else if, то есть каждая следующая проверка учитывает результаты всех предыдущих. И, быстро просмотрев этот код, трудно понять, правильный он или нет. А в нем на самом деле допущена ошибка — вот здесь. Вот здесь вот: если B = 0, ты все равно разделим на 0. Если вы внимательно посмотрите вот этот вот кусок кода, вот этот кусок кода, то вам тоже станет очевидно, что случай, когда B = 0 раньше не рассматривается, и мы действительно можем здесь поделить на 0. Давайте теперь рассмотрим другое, декомпозированное решение этой задачи. Оно у меня тут заготовлено. Мы его просто вставим. На какой идее основано это решение? Оно основано на том, что если коэффициент A ≠ 0, то мы решаем квадратное уравнение, мы четко знаем: нам дано квадратное уравнение, нам его надо решить. Если коэффициент A = 0, то нам надо решить линейное уравнение. Нам больше не надо думать о квадратном, мы просто решаем линейное уравнение. Поэтому, после того как мы считываем коэффициенты, мы первым делом проверяем, а равен ли 0 коэффициент A. И если он не равен, то мы вызываем SolveQuadradicEquation, которое будет решать нам квадратное уравнение. А если же коэффициент A = 0, то мы вызываем функцию SolveLinearEquation, которая решает линейное уравнение. И функция, которая решает линейное уравнение, она вот такая простая. Она просто выводит −C / B. И когда мы эту функцию пишем, очень трудно забыть про то, что мы можем разделить на 0, очень трудно забыть проверить коэффициент B на равенство 0. Таким образом, когда мы сразу выполнили декомпозицию, мы разбили нашу простую задачу, в данном случае две маленьких, и решая каждую из них, нам проще учесть все возможные варианты и проще не допустить ошибок. Поэтому Я всячески призываю вас сразу выполнять декомпозицию своих программ. Но нам самом деле, и юнит-тесты лучше тоже писать сразу, еще до того как тестирующая система не приняла ваше решение. Конечно, я уверен, многие возмутятся, скажут: зачем мне тратить время на какие-то юнит-тесты, я быстренько написал, мне все понятно, я и отправил. И, в общем-то, это аргументы весомые. Но в предварительном написании юнит-тестов тоже есть немало преимуществ. Во-первых, когда вы разрабатываете юнит-тесты, то вы сразу продумываете все варианты использования вашего кода. И все крайние случаи входных данных, которые он может получить. Вообще, юнит-тест на практике, если говорить о больших проектах, юнит-тесты лучше разрабатывать еще до того, как реализовали сам код, потому что вы, разрабатывая тесты, продумали все варианты использования, учли все подводные камни и приступили к реализации, уже держа в голове большую картину. Вы понимаете, какие проблемы могут возникнуть при реализации той или иной функции или класса. Дальше. Тесты позволяют вам сразу проконтролировать корректность вашей реализации. Вы написали какой-то код, а у вас под него уже есть набор юнит-тестов. Вы взяли их, запустили и сразу понимаете, правильно вы сделали или неправильно. Это опять же очень важно, если говорить о разработке больших проектов, потому что часто саму вот эту большую систему, которую вы разрабатываете, ее бывает достаточно трудно запустить — ей нужны какие-нибудь базы данных, подключение к Интернету, еще какие-нибудь вещи, файлы. А юнит-тесты — кроме вашей функции, обычно ничего и не надо, поэтому вы можете быстренько их запускать и быстренько контролировать: а вот этот кусочек программы я написал правильно, я в нем уверен, потому что у меня есть набор тестов, и они говорят, что там все нормально. И, наконец, вещь, которая, наверное, уже точно не относится к нашим задания по программированию, но очень актуальна для работы в больших проектах — это то, что обширный, полный набор тестов позволяет вам убедиться, что вы не сломали ничего из того, что раньше работало, потому что вы добавляете какую-то функциональность, и в процессе работы вносите изменения в код, который раньше существовал. Вам нужен способ контроля, который вам позволит убедиться, что вы не сломали что-то из того, что раньше работало. Набор тестов вам позволяет понять, что все, что работало раньше, оно и продолжает работать как раньше. Именно поэтому в наших задачах, я надеюсь, вы будете активно использовать наш юнит-тест фреймворк и покрывать юнит-тестами решения своих задач, чтобы набрать этот навык, чтобы научиться тестировать функции, тестировать классы, тестировать свои программы, потому что когда вы беретесь за разработку какого-то большого проекта, этот навык очень полезен и позволяет сэкономить уйму времени на отладке и поиске багов.