Урок №25. Разработка ваших первых программ

  Юрий  | 

  |

  Обновл. 26 Янв 2022  | 

 108720

 ǀ   38 

При написании программ, у вас, как правило, есть какая-то проблема, которую нужно решить. Новички очень часто спотыкаются на этапе преобразования идеи решения проблемы в реальный код. Но самое главное, что вам нужно запомнить — разработка программы выполняется перед этапом написания её кода.

Во многих отношениях, программирование — это как архитектура. Что произойдет, если вы попытаетесь построить дом без соблюдения архитектурного плана? Дом может вы и сумеете построить, но какой он будет: кривые стены, протекающая крыша и т.д. Аналогично, если вы начнете программировать что-нибудь серьезное перед тем, как составите план, то очень скоро обнаружите, что ваш код имеет очень много проблем, на решения которых вы потратите гораздо больше времени/усилий/нервов, нежели на изначальное составление хорошего плана.

Шаг №1: Определите проблему

Первое, что вам нужно сделать — это определить проблему, которую решит ваша программа. В идеале, вы должны сформулировать это одним или двумя предложениями. Например:

   Я хочу написать программу-справочник для удобного хранения и редактирования всех телефонных номеров и звонков.

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

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

Хотя этот шаг кажется очевидным, но он также очень важен. Самое худшее, что вы можете сделать — это написать программу, которая делает не то, что вам нужно!

Шаг №2: Определите свой инструментарий, цели и план бэкапа


Для опытных программистов на этом этапе будет еще немало дополнительных пунктов:

   Какая ваша целевая аудитория и какие у нее потребности?

   На какой архитектуре/ОС ваша программа будет работать?

   Какой инструментарий вы будете использовать?

   Будете ли вы разрабатывать программу в одиночку или в составе команды?

   Анализ требований.

   Определение стратегий тестирования/обратной связи/релиза.

   Создание плана бэкапа в случае неожиданных проблем.

Новички, как правило, большим количеством вопросов не задаются: «Пишу программу для собственного использования, в одиночку, на своей операционной системе, с помощью своей IDE, пользоваться этой программой буду только я». Всё просто.

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

   Отправить самому себе E-mail с кодом (прикрепить как файл).

   Скопировать в Dropbox или в любое другое облако.

   Перенести на внешнее запоминающее устройство (например, на портативный жёсткий диск).

   Скопировать на другой компьютер в локальной сети.

   Воспользоваться системами контроля версий (например, GitHub, GitLab или Bitbucket).

Шаг №3: Разбейте проблему на части

В реальной жизни нам часто приходится выполнять очень сложные задачи. Понять, как их решить, также бывает очень трудно. В таких случаях можно использовать метод деления на части (или метод «от большого к малому»). То есть, вместо того, чтобы решать одну большую сложную задачу, мы разбиваем её на несколько подзадач, каждую из которых проще решить. Если эти подзадачи все еще слишком сложные, то их также нужно еще раз разбить. И так до тех пор, пока мы не доберемся до точки, где каждая отдельно взятая задача — легко решаема.

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

   Написать доклад о картошке

Это довольно большая задача, давайте разделим её на подзадачи:

   Написать доклад о картошке

   Поиск информации о картошке

   Создание плана

   Заполнение каждого пункта плана подробной информацией

   Заключение

Это уже проще, так как теперь мы имеем список подзадач, на которых можем сосредоточиться в индивидуальном порядке. Тем не менее, в данном случае, «Поиск информации о картошке» звучит немного расплывчато, нужно дополнительно разбить и этот пункт:

   Написать доклад о картошке

   Поиск информации о картошке

   Сходить в библиотеку за книжками о картошке

   Поискать информацию в Интернете

   Делать заметки в соответствующих разделах из справочного материала

   Создание плана

   Информация о выращивании

   Информация об обработке

   Информация об удобрениях

   Заполнение каждого пункта плана подробной информацией

   Заключение

Выполняя каждый подпункт этого задания, мы решим одну большую задачу.

Есть еще один метод создания иерархии — метод «от малого к большому». Рассмотрим на примере.

Большинство из нас вынуждены ходить на работу (школу/университет) по будням. Предположим, что нам нужно решить проблему «от постели к работе». Если бы вас спросили, что вы делаете перед тем, как добраться на работу, вы бы ответили примерно следующее:

   Выбрать одежду

   Одеться

   Позавтракать

   Ехать на работу

   Почистить зубы

   Встать с постели

   Приготовить завтрак

   Принять душ

Используя метод «от малого к большому», мы можем сгруппировать задания и создать иерархию:

   От постели к работе

   Спальня

   Встать с постели

   Выбрать одежду

   Одеться

   Ванная

   Принять душ

   Почистить зубы

   Завтрак

   Сделать завтрак

   Позавтракать

   Транспорт

   Ехать на работу

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

Шаг №4: Определите последовательность событий


Теперь, когда ваша программа имеет структуру, пришло время ответить на вопрос: «А как же связать все эти пункты вместе?». Первый шаг заключается в определении последовательности событий. Например, когда вы просыпаетесь утром, в какой последовательности вы выполняете вышеперечисленные дела? Скорее всего, в следующей:

   Встать с постели

   Выбрать одежду

   Принять душ

   Одеться

   Приготовить завтрак

   Позавтракать

   Почистить зубы

   Ехать на работу

Если бы мы создавали калькулятор, то последовательность заданий выглядела бы следующим образом:

   Получить первое значение от пользователя

   Получить математическую операцию от пользователя

   Получить второе значение от пользователя

   Вычислить результат

   Вывести результат

Этот список определяет содержимое функции main():

Или, в случае с калькулятором:

Шаг №5: Определите данные ввода и данные вывода на каждом этапе

После определения иерархии задач и последовательности событий, нам нужно определить, какими будут данные ввода и данные вывода на каждом этапе.

Например, первая функция из вышеприведенного примера — getUserInput(), довольно проста. Мы собираемся получить число от пользователя и вернуть его обратно в caller. Таким образом, прототип функции будет выглядеть следующим образом:

В примере с калькулятором, функции calculateResult() требуется 3 ввода данных: два числа и 1 математический оператор. При вызове calculateResult() у нас уже должны быть 3 фрагмента данных, которые мы будем использовать в качестве параметров функции. Функция calculateResult() вычисляет значение результата, но не выводит его. Следовательно, нам необходимо вернуть этот результат в качестве возвращаемого значения обратно в caller, чтобы другие функции также имели возможность его использовать.

Учитывая это, прототип функции становится следующим:

Шаг №6: Позаботьтесь о деталях


На этом этапе нам нужно реализовать каждый подпункт из нашей иерархии задач. Если вы разбили задание на достаточно мелкие кусочки, то выполнить каждый из этих кусочков будет несложно. Например:

Шаг №7: Соедините данные ввода с данными вывода в программе

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

Согласитесь, что вариант, приведенный выше, читабельнее варианта без использования временных переменных (см. ниже):

Рассмотрим готовую версию программы-калькулятора, её структуру и перемещение данных:

Здесь есть несколько концепций, которые мы еще не рассматривали: условное ветвление if и использование оператора равенства == для сравнения двух элементов. Не беспокойтесь, если вы это не понимаете — мы всё это детально рассмотрим на следующих уроках.

Советы

Пускай ваши первые программы будут простыми. Очень часто новички ставят слишком высокие планки для своих первых более-менее серьезных программ. Например, «Я хочу написать игру с графикой, звуком, монстрами, подземельями и городом, в котором можно будет продавать найденные вещи». Если вы попытаетесь написать что-нибудь подобное в начале вашего пути как программиста, то очень скоро у вас пропадет любое желание программировать. Вместо этого, пускай ваши первые цели/задания/программы будут попроще, например, «Я хочу написать программу, которая отображала бы 2D-поле на экране».

Добавляйте новый функционал со временем. Как только вы написали простенькую программу, которая работает (даже без сбоев), то следующим вашим шагом должно стать добавление нового функционала. Например, когда вы сможете отображать 2D-поле на экране — добавьте персонажа, который сможет ходить по этому полю. После того, как вы уже сможете ходить — добавьте стены, которые будут препятствовать вашему движению. После того, как у вас будут стены — постройте из них город. После того, как у вас будет город — добавьте персонажей-продавцов. При таком подходе на вас не наваливается всё сразу и вы знаете с чего начинать, что делать дальше, в какой последовательности и т.д.

Фокусируйтесь только на одном задании в определенный промежуток времени. Не пытайтесь сделать всё и сразу, не распыляйтесь на несколько задач одновременно. Сосредоточьтесь на одном. Лучше иметь одно выполненное задание и пять невыполненных, нежели шесть частично выполненных заданий. Если вы рассеиваете свое внимание в нескольких направлениях, то и ошибок будет много.

Тестируйте каждую новую часть кода. Начинающие программисты часто пишут программу за один присест. Затем, при компиляции проекта, получают сотни ошибок. Поэтому, после написания определенной части кода — сразу компилируйте и тестируйте её. Если ваш код не будет работать, то вы будете знать, где находится проблема, и исправить её будет намного легче. Как только вы убедились, что ваш код рабочий — переходите к написанию следующей части, а затем repeat. Тестирование может занять больше времени, но в конечном итоге ваш код будет работать так, как вам нужно.

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

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (1 054 оценок, среднее: 4,96 из 5)
Загрузка...

Комментариев: 38

  1. Денис:

    Здраствуйте, я хотел вот что узнать. В вашем калькуляторе можно ввести только два значения и один оператор с 4 предложенных. Сделать больше операторов ( добавить корень, степень и т.д. ) это легко, но как сделать так, чтобы можно было вводить разное количество значений и операторов? Кроме того, данный калькулятор делит только с остачей и выводит только целые значения (например 5/2 выводит 2).  Как сделать, чтобы результат выводился с числами после запятой?

    1. shmelkpi:

      используя тип данных double вместо int

  2. Анатолий:

    на случай если кому интересно что можно сделать если пользователь ввел неправильную операцию:

  3. Игнат:

    А так плохо или хорошо ответьте пожалуйста.
    1- плохо.
    5- отлично.

    1. Стас:

      для вывода текста на русском используй setlocale(LC_ALL, "Russian");
      а так то все хорошо

      1. Виктор Климец:

        Стас, извините, но ваш совет   корректен только наполовину.  Действительно, для вывода можно использовать  setlocale(LC_ALL, «Russian»); но он не работает на ввод — с  оператором cin  :-(( ….

        ПОЭТОМУ:

  4. ...:

    Здравствуйте, у меня проюлема в коде, ошибок нет, но выводит в результате "-1", почему?

    1. Алексей:

      А вот посмотрите-ка внимательно: вы присваиваете значение переменной result, приравнивая его к результату функции math2 с аргументами input1, op, input2. (То есть int result = math2(input1, op, input2); ). НО в функции math2 у вас другая последовательность аргументов: int math2(int op, int x, int y). Таким образом в функции math2 значению op будет присвоено значение input1, а оно наверняка больше чем 4, поэтому ваши условия не выполняются, и возвращается -1.

  5. Алексей:

    Юрий, в вашей программе — калькуляторе при вводе пользователем неверного числа или символа — на экран выводится "-1", что некорректно, и непонятно пользователю. Нужно добавить дополнительное условие для проверки корректности введенных данных сразу в первой функции, раз уж начали применять условия. При вводе пользователем неверного символа сразу выводить на экран "Fucken shit!" или "Введенное значение некорректно", и делать возврат в функцию ввода. Так будет удобнее и понятнее пользователю, и избавит от ошибок в типах данных. Тогда пользователю не удастся случайно "сломать" программу, и это тоже показатель.

    1. Jaroshevskii:

      Как правильно сделать возврат ввода? Просто короме do while или goto ничего на ум не приходит.

  6. Михаил:

    1. КАРЫЧ:

      ЭМ… А зачем использовать namespase std и в итоге писать std::

      1. Константин:

        Карыч, вот мы тут с тобой стебёмся над человеком, а вдруг это будующий Лобачевский? Ведь смог же человек обосновать, что параллельные прямые пересекаются, а этот джентельмен обоснует существование диагонали в треугольнике!

        1. Фото аватара Дмитрий Бушуев:

          Раз уж на то пошло, имея… 🙂

          >>Ведь смог же человек [Лобачевский] обосновать, что параллельные прямые пересекаются.

          Лобачевский такого не утверждал. Он обосновал следующее: если взять прямую и не лежащую на ней точку, то через эту точку можно провести несколько(!) других прямых, параллельных изначально взятой прямой. В "школьной" же геометрии (или как её еще называют — "Евклидовой"), через такую точку можно провести только одну(!) прямую.
          P.S.: У Лобачевского даже есть такое понятие, как — угол параллельности 🙂

    2. Константин:

      Михаил, хотел тебе подсказать, мол, зачем писать две функции для получения данных от пользователя, ну и всё такое, но наткнулся на диагональ в треугольнике — сейчас сижу, гружусььь…

  7. Анастасия:

    Попыталась объединить все знания, полученные за 25 уроков, и вот что вышло. (всё работает, но хотелось бы получить рекомендации на счёт практичности и релевантности, которые были бы более выгодны в сложных проектах).

    Основной файл с main(), в котором вызываются функции MyFirstProgramm.cpp

    Файл с задачей на взятие первого значения firstStep.cpp

    Файл с задачей на взятие оператора Operator.cpp

    Снова на взятие значения, но тут уже другой текст "просьбы", так сказать, для разнообразия (но можно было бы снова вызвать функцию firstStep()) thirdStep.cpp

    Теперь самое важное — произвести вычисления. Можно было бы и с помощью цикла, но выбор пал именно на switch. Determination.cpp

    И сам вывод. Output.cpp (Да, не лучшее название…)

    А так же файл заголовка Functions.h . Работаю на VS поэтому обошлась #pragma once

    На счёт странных пропусков:
    Хотелось как-то упорядочить вывод строк, но ничего другого не пришло в голову, кроме как оставить пробелы в нужных местах.

  8. zashiki:

    Хотела объединить с уроком "многофайловые программы", все работает. Но есть какие то замечания?

    Заголовочный файл, объявляющий функции: opstates.h:

    файл для получения чисел: get1n.cpp:

    файл для получения знаков: get2o.cpp:

    файл для возвращения результата: get3r.cpp:

    файл main.cpp:

  9. Сергей:

    Тест. Как вставить программный код чтобы он был отформатирован как в примерах?

    Отформатируется ли текст автоматически?

    1. Фото аватара Юрий:

      Ребята, уже не первый раз говорю. Отправляйте код, как есть — всё форматирование выполняется на моей стороне с помощью определённого плагина. От вас никакого форматирования текста/кода не требуется — только copy/paste. Остальное я сделаю сам.

  10. Михайло:

    Но как в програме калькулятор одние функции знают о переменних с других функций у них же локальная область видимости.

    1. Cerberus:

      Функции знают ровно то, что им передано в качестве аргументов. Где-то этот принцип нарушается, на Ваш взгляд?

  11. Денис:

    Я немного иначе, по простому сделал, может и не правильно, но работает. Из следующих уроков ничего нет, только базовые знания этого курса:) Спасибо за доходчивую статью:)

    Если где-то глупо или допустил ошибку, я открыт для критики, это даже полезно на данном этапе:) Только очень прошу, критикуйте доходчиво:)

    1. Nikita:

      А где собственно изменения?

  12. Вячеслав:

    вот посмотрите, что скажете?

    1. Alexey:

  13. Вячеслав:

    у меня проблем нет с калькулятором все работает как швейцарские часы

    1. ThatSameGuy:

      Прямо как часы ?
      i.imgur.com/5erlMgw.png
      (Фикситься очевидно превращением a и b в float)

  14. Виктор:

    Здравствуйте. При написании калькулятора я решил подключить кириллицу. Прописал так как вы писали. Можете объяснить что означают эти строки:

    И, еще я чет не могу догнать когда нужно использовать int, а когда void. Можете как-то популярнее мне объяснить? И, посмотрите на мои комментарии строк, правильно ли я все понимаю. Заранее спасибо за ответ.

    1. Константин:

      Виктор, каждое второе if выбросить, а взамен блок:

      и тип int поставь перед переменной result, т.е. д.б.

      а вообще и так всё работает, и понятие есть А 1251 это, видимо, какой-то параметр, который включает кирилицу во встроенных функциях ввода и вывода в — , на — консоль.

    2. Константин:

      Даже не так: 1251 — что-то радикальнее, чем просто ввод-вывод кирилицы на экран, делает — меняет кодировку проекта (хотя деталей этого я не знаю)

  15. Наиль:

    Написал примерно такой же калькулятор.первое число принимается,операция принимается,второе число принимается,и дальше ничего ответ не выдаётся что делать?

  16. Дима:

    Супер) Все как в жизни

  17. painkiller:

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

    1. Фото аватара Юрий:

      Спасибо, главное — не останавливаться и продолжать дальше.

  18. Семён:

    Спасибо , огромное !
    Конечно , кое что не понятно , НО пока . С таким объяснением , а точнее перевод , понять не будет сложно , всё постепенно !

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  19. Иван:

    Красава, сразу видно что сам наступал на грабли и решил нас предостеречь, от лишних хлопот. мало того что умеешь объяснить, так ещё и убедить! Не возникает желание зделать принципиально не так! Спасибо автор.

    1. Фото аватара Юрий:

      Автор не я, но со всем этим согласен 🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *