С++, 1 курс, 3 группа, 2 семестр, 2016/17

Материал из CSC Wiki
Перейти к:навигация, поиск

Страница курса.

Преподаватель: Егор Суворов (egor_suvorov@mail.ru, vk.com/egor.suvorov)

Префикс в теме письма: [C++TA]

Вопросы можно писать либо в ВК, либо на почту. В ВК быстрее отвечаю. Если вопросов много или они требуют вдумчивого ответа - лучше на почту.

Есть чат в ВК, там публикуются ответы на часто задаваемые вопросы, комментарии по лаборатным/домашкам и прочее. Также могут появляться общие комментарии по программированию и плюсам в частности. Я их буду время от времени переносить в раздел ниже, если заметили, что чего-то не хватает - перенесите. Копировать мои сообщения самостоятельно не стоит.

Общие знания

Примеры с занятий лежат на GitHub.

Deadline и сдача лабораторных

По поводу deadline и попыток сдачи лабораторных (для домашек будут особенности):

  1. Дедлайн железобетонный в 23:59, если иное не объявлено в чатике. Дедлайн проверяется по времени коммита в SVN (тикет в Trac стоит обновить просто в разумное время). Если проверяю до дедлайна - беру последнюю версию. Если после - последнюю, которая закоммичена до 23:59. Проверяйте, что все файлы добавились в репозиторий.
  2. Проверяю по возможности, одна попытка есть всегда, если успеваете отреагировать на замечания до дедлайна - то получается две, три, и так далее.
  3. К сожалению, пока что проверяю только в воскресенье днём-вечером, так что скорее попытка одна. Возможно, в будущем это изменится.
  4. Жёстких ограничений на количество попыток сдачи нет, но спамить не стоит. Процесс сдачи итеративный, но не интерактивный.
  5. Вы можете задавать вопросы в течение недели по любой теме: теория, "как правильно", "можно ли сделать так", "почему вот такой вот код на таком тесте выдаёт X, а не Y" (обязательно наличие кода и каких-то подробностей вроде "вот отчёт Valgrind и тест из одной строчки, с int работает, с Product - нет" - как на StackOverflow), "есть ли бросающиеся в глаза стилистические замечания?"
  6. Про проверку стиля в течение недели: не обещаю, что буду проверять так же внимательно, как и на "реальной" попытке сдачи. Можно присылать и куски кода, и общие вопросы ("куда класть тесты для вектора"), и весь проект целиком. Чем меньше кусок кода - тем быстрее и качественнее получите ответ.
  7. Для второй попытки сдачи требуется переоткрыть карточку в Trac: состояние reopened, "ожидает проверки", версия - 2.0 или 3.0.
  8. Можно использовать фишки, которые мы ещё не проходили, в том числе из C++11. Однако корректность их использования тоже будет проверяться. Умеете использовать (или хотите научиться) - вперёд.
  9. "Задача сдана" и "задача не сдана" - это условные состояния в Trac, которые я ставлю субъективно (например, прошло ли все автоматические тесты то, что должно было пройти). Они не влияют ни на баллы, ни на отчётность.
  10. Ставится максимальный балл за все попытки. Баллы за попытку считаются независимо. Например, если в одной вы получили 5 баллов за корректность и 0 за стиль, а в другой - 2 за корректность и 2 за стиль, то вы получите максимум из 5+0 и 2+2, т.е. 5.
  11. Если ваше решение не прошло тесты, то на код я по умолчанию не смотрю, за стиль ставятся условные 0 баллов. Это сделано для того, чтобы сэкономить моё время: наверняка код будет сильно изменяться в процессе исправления багов. Если вы всё-таки хотите, чтобы я прочитал код в попытке, которая не проходит тесты, вам нужно об этом явно попросить (переоткрыв тикет и написав комментарий вроде "проверьте, пожалуйста, стиль в попытках таких-то"). Несколько примеров:
    • Вы прислали код, он не прошёл тесты, вы попытались исправить и у вас не получилось до дедлайна. Тогда в любой момент до 12:00 понедельника вы можете меня попросить прочитать код и выставить баллы за стиль.
    • Вы прислали код, он не прошёл тесты, вы сделали ещё одну попытку, но тесты всё ещё не пройдены. Тогда вы можете попросить меня прочитать код любой из попыток (или даже обеих).
    • Вы прислали код, он не прошёл тесты, вы сделали ещё одну попытку, она прошла все тесты. Тогда я буду проверять стиль кода именно в ней. Впрочем, вы всё ещё можете попросить меня проверить стиль кода в предыдущей посылке, но вряд ли это осмысленно - в ней было меньше баллов за корректность и вряд ли будет больше баллов за стиль (хотя кто знает).
  12. Тесты могут изменяться и добавляться по ходу проверки (если они всё ещё соответствуют заданию) по мере обнаружения всё более и более интересных багов. Уже выставленные баллы при этом не уменьшаются, если только они не были выставлены с пометкой "предварительно".

Полезные правила

  • Правило трёх/пяти. На && пока забиваем - это move-семантика, появилась в C++11, будет в конце семестра, там весело (теоретически похоже на ад, если стандарт читать, на практике применять просто). Пока что просто игнорируйте соответствующий конструктор и оператор.
  • delete и delete[] *корректно* разбирают случай "аргумент - NULL", просто ничего не делает. Дополнительные проверки не нужны.
  • Если уж и копипастите - учитывайте эффект последней строки.
  • Не используйте #define для объявления констант и функций:
    1. Сколько-то причин перечислено тут
    2. #define работает на уровне препроцессора, до компилятора. Он довольно глупый: берёт и заменяет все вхождения одного слова на другое. Например, если вы используете его для объявления функций или констант, то это имя вы потом вообще нигде использовать не сможете:
      1. http://ideone.com/cCMKQ9
      2. http://ideone.com/b6q8ew
      3. http://ideone.com/bpJfG7
    3. Если вы используете #define для объявления функций, то из предыдущего пункта автоматически следует невозможность перегрузок функций.
    4. В #define очень легко забыть поставить скобки вокруг аргументов или результата: http://ideone.com/AXjnVN
    5. #define объявляется в одном месте, а подставляется в другом. Никак не проверяется, что мы не опечатались внутри #define - может повезти: http://ideone.com/H8K2pg
    6. Аналогично, даже если мы не опечатались, то всё равно может произойти что-то неожиданное: http://ideone.com/XZdj3f
    7. #define плевать хотел на все фигурные скобки и пространства имён. Объявили в одном месте - а он действует до конца файла. Нельзя завести "локальный" #define (кроме как руками прекращать его действие при помощи #undef).
    8. Из предыдущего пункта следует, что при наличии #define в заголовочном файле все эти проблемы огребает не только ваш заголовочный файл, но и основной `.cpp` файл, а также все заголовочные файлы, включённые после вашего: http://ideone.com/q9CKRA

Про ООП и разделение кода

Общий совет: если вы не можете написать юнит-тест для какого-то объекта/функции/куска программы, потому что надо слишком много всего настраивать, скорее всего, этот кусок программы надо разделить на несколько.

Например, если у вас парсинг аргументов находится в main, то вы не сможете написать к нему unit-тесты. Если у вас есть один объект "архиватор по Хаффману", который самостоятельно делает всё на свете от открытия файла до (де)архивации и подсчёта статистики, то сложно будет тестировать отдельные части этого объекта. Получаются по духу скорее интеграционные и функциональные тесты вместо юнит-тестов.

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

Типичный пример плохого класса - так называемый God Object. Это класс, который "делает всё" в вашей программе.

Другой типичный пример не самого хорошего класса на C++ - класс, у которого отсутствует какое-либо состояние. Тогда это просто сборник функций, лучше их сразу функциями и сделать.

Полезные утилиты

  • Для автоматического форматирования программ на C++ можно пользоваться либо какой-нибудь IDE (я верю, что Visual Studio, Eclipse, NetBeans, CodeBlocks и CLion все умеют форматировать; если не так - поправьте), либо консольными утилитами вроде clang-format или astyle. В качестве референсного можете использовать Google Code Style.
  • gcov может показывать покрытие строчек кода тестами. Документация тут, читать пример со слов "Running the program will cause profile output to be generated.". Будьте осторожны с оптимизациями - если функция заинлайнена, то она в покрытии не показывается.

Критерии получения статуса "задание зачтено" для Хаффмана (большое дз)

Критерии

Чтобы Хаффман был "сдан" в смысле допуска к экзамену, надо, чтобы выполнялись все нижеследующие условия:

  1. Набрать >= 30% баллов (т.е. хотя бы 10).
  2. Чтобы присутствовал хотя бы один автоматический unit/функциональный тест, который бы покрывал все строчки кода, которые имеют прямое отношение к построению дерева. "Имеет отношение" определяется на мой субъективный вкус. Скажем, чтение битов из потока, парсинг аргументов и чтение дерева из файла отношения к этому не имеет, а вот если у вас при построении дерева разобран отдельный случай "в файле ровно два различных символа" - его надо покрывать (лучше отдельным тестом, конечно). Пример теста, который наверняка всё покроет: сжать конкретную строчку, разжать, посмотреть, что совпало.
  3. Чтобы этот тест собирался и проходил.

На данный момент (28.04) статус в Trac может не соответствовать этим критериям, так что на него не ориентируйтесь. После проверки третьей попытки статусы будут приведены в соответствие.

Комментарии про допуск к экзамену

В соответствии с правилами, объявленными в начале семестра (письмо от 13 февраля), для получения допуска к экзамену требуется "сдать" все домашние задания. В этом семестре оно получилось только одно - Хаффман, криетрии "сдачи" описаны выше.

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

Примерные правила оценки лабораторных

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

Лабораторная 13 (массив со специализацией)

Правила описаны в задании.

Лабораторная 12 (вектора)

10 баллов распределяются так:

  1. За прохождение тестов и Valgrind (от 0 до 3 баллов):
    • 0 баллов, если программа не компилируется или падает.
    • 1 балл, если падает при запуске ваших тестов с каким-нибудь типом, кроме int и Product (удовлетворяющим, тем не менее, всем разумным требованиям).
    • 2 балла, если ваша реализация my_vector не прошла наши тесты.
    • 3 балла, если все тесты пройдены.
    • -1 балл, если есть утечки памяти, но нет более страшных проблем.
    • -2 балла, если есть undefined behavior вроде чтения из неинициализированной памяти (тогда -1 балл за утечки памяти не ставится).
  2. За тесты:
    • 2 балл, если покрыты все строчки.
    • 1 балл, если покрыты все методы.
    • 0 баллов иначе.
  3. За качество и корректность интерфейсов классов (включая поля) и функций:
    • 2 балла, если претензий нет.
    • 1 балл, если в целом идея верная, но сделали что-то не слишкои опасное (открыты лишние методы которые не могут нарушить инвариант, просто есть лишние методы), или что-что, что обнаружится на этапе компиляции (забыли const qualifier).
    • 0 баллов, если интерфейс опасен (забыли закрыть/реализовать оператор присваивания/конструктор копирования, открыт метод, который может нарушить инвариант, пространство имён после включения заголовка меняется слишком неожиданно).
  4. За качество и корректность реализации ставится от 0 до 3 баллов (субъективно). Снимаются за, например:
    • -1 балл, если есть обширное дублирование кода, от которого можно избавиться (например, функцией).
    • -1 балл за серьёзное несоответствие "традициям" языка C++ или за неожиданную логику программы.
    • -1 балл за сильную (не)асимптотическую неэффективность (например, лишние вызовы конструкторов и выделения памяти).

28.02.2017 было произведено перемасштабирование: если баллы за пункты равнялись , , , , и - флаг "верно ли, что " (0 или 1; т.е. покрыты ли все методы), а - флаг "верно ли, что " (0 или 1; т.е. покрыты ли все строчки), то новый балл равен . Суммарный балл округляется до ближайшего целого, 0.5 округляется вверх.

Лабораторная 11 (I/O и полиморфизм)

Итак, 10 баллов распределяются (до 27.02.2017) примерно следующим образом:

  1. За прохождение тестов:
    • 3 балла, если прошло всё и сразу.
    • 2 балла, если потребовалось косметически поменять формат вывода текста (вроде `SalaryManager` и `Salary Manager`).
    • 1 балл, если работает только текст (возможно, после косметических исправлений) или программа исходно не компилировалась.
    • 0 баллов, если не удалось простыми изменениями заставить заработать.
  2. За Valgrind:
    • 2 балла, если претензий нет.
    • 1 балл, если есть только утечки памяти.
    • 0 баллов, если есть undefined behavior вроде чтения из неинициализированной памяти.
  3. За качество и корректность интерфейсов классов (включая поля) и функций:
    • 2 балла, если претензий нет.
    • 1 балл, если в целом идея верная, но сделали что-то не слишкои опасное (открыты лишние методы), или что-что, что обнаружится на этапе компиляции (забыли const qualifier).
    • 0 баллов, если интерфейс опасен (невиртуальный деструктор, забыли закрыть оператор присваивания/конструктор копирования).
  4. За качество и корректность реализации ставится от 0 до 3 баллов и снимаются за:
    • -1 балл, если есть обширное дублирование кода, от которого можно избавиться (например, вынести в предка).
    • -1 балл за скрытые ограничения (вроде фиксированной максимальной длины строки).
    • -1 балл за серьёзное несоответствие "традициям" языка C++ или за неожиданную логику программы (нумерация массивов с единицы, реализация больших нешаблонных методов в header'ах, лишний код).

28.02.2017 было произведено перемасштабирование: если баллы за пункты равнялись , , , , то новый балл равен . Суммарный балл округляется до ближайшего целого, 0.5 округляется вверх.