Opened 4 years ago
Last modified 4 years ago
#970 assigned ожидается проверка
HW #3
Reported by: | Solovyev Gleb | Owned by: | Egor Suvorov |
---|---|---|---|
Component: | HW #3 (Huffman) | Version: | 3.0 |
Keywords: | Cc: |
Description
Change History (6)
comment:1 Changed 4 years ago by
comment:2 follow-up: 3 Changed 4 years ago by
Version: | 1.0 → 2.0 |
---|
Да, было такое. Это вторая попытка.
comment:3 Changed 4 years ago by
Replying to Egor Suvorov:
Да, было такое. Это вторая попытка.
Да, верно, просто не обратил внимания при создании тикета.
comment:4 Changed 4 years ago by
Owner: | changed from Egor Suvorov to Solovyev Gleb |
---|---|
Type: | ожидается проверка → ожидаются исправления |
Проверялась ревизия 4064.
Третья попытка разблокирована.
Корректность 8/9
- Не хватает открытия файлов в бинарном режиме: не будет работать под Windows.
Тесты 7.9/8
- В среднем красота! Разве что архитектура мешает сделать простые тесты: приходится очень много проверять.
test HuffmanNode
- это два разных тестах.
Архитектура 2.5/5
- Последовательности бит - точно не
string
. Скорееvector<bool>
, чтобы пользоваться статической типизацией: есть значение - точно битовая строка. - У объектов не должно быть "неинициализированного" состояния, используйте конструкторы. Смотрю на
Symbol
,CLI
... - Очень много геттеров, сеттеров, лишних методов, изменяемых структур. Невозможно отследить инварианты, читая только
.h
-файл. Пример: смотрим на тестtest doNextDecryptDfsStep() & curNode checker
, там жесть какая-то и проверяются три штуки одновременно.- Можно стереть/заинлайнить половину кода, станет проще и короче (например,
getNodeCmp
). - Лучше иммутабельные структуры: один раз вызвали нужный конструктор, потом только геттеры. Или агрегаты: все поля публичные, не надо извращаться с геттерами/сеттерами лишний раз.
- Можно стереть/заинлайнить половину кода, станет проще и короче (например,
- Много где используются "специальные значения" (типа нулей или пустых строк) вместо
std::optional
. - Имхо, не стоит оборачивать исключения потоком в свои: это усложняет код и не упрощает обработку ошибок.
- Аналогично, в
HuffmanArchiver
не надо ловить и перебрасывать исключения. Вы их не обрабатываете, не можете обработать, и не добавляете полезной информации. Пусть летит так. А ещё вы ловите вообще всеstd::iostream::failure
и заявляете, что это всегда ошибка чтения - не верю. Поменяю пару строк где-то в недрах и этот инвариант нарушится.
- Аналогично, в
BitManips
- Неожиданно, что нет способа записать один бит. Точнее, есть, но приватный. Это же самая базовая операция. А записать байт/строчку - это уже даже свободные функции могут быть плюс ADL.
- В деструкторе
writer
стоит проверить, что записано целое число байт.
huff_cli
/huff_logic
/huff_archiver
-->huffman::cli
...CLI
- Лучше заменить
parseArgs
наCLI
. Тогда уCLI
станет очень простое иммутабельное состояние, не надо будет думать "а что если оно не проинициализировалось". - В C++ не принято отдельно передавать массив и его размер. И сишные строки тоже.
vector
/span
/string_view
... Заодно в тестах не надо будет создавать массив.
- Лучше заменить
HuffmanNodePtr
объявляется очень много раз.Huffman
- Несимметрично, что в этом файле столько всего. Разделение тестов на три файла намекает, что, возможно, этот файл тоже следовало распилить. Ну или тесты в один файл положить. Сейчас неясно, где искать тесты для классов/структур из этого файла.
Symbol
- уберите геттеры/сеттеры, это просто структура-агрегат. Никаких дополнительных проверок нет. Но ещё точно стоит сделать
explicit
-конструктор какой-нибудь, чтобы не было неинициализированного состояния.- Довольно странно, что структура "Символ" не содержит, собственно, символ. Не используется ли она только как часть
HuffmanNode
? Если да - то их стоит объединить.
- Довольно странно, что структура "Символ" не содержит, собственно, символ. Не используется ли она только как часть
- уберите геттеры/сеттеры, это просто структура-агрегат. Никаких дополнительных проверок нет. Но ещё точно стоит сделать
HuffmanNode
- Конструктор от двух детей часто применяется с двумя пустыми детьми. С частотой и без символа. Это что-то очень непонятное. И как минимум стоит распилить этот конструктор на несколько частей: один для листов, один для внутренних верших, они разные. Причём второй может ещё и частоту посчитать.
- Конструктор от
Symbol
на самом деле является конструктором от частоты. Незачем завязываться наSymbol
. - Слишком, слишком много методов/геттеров/сеттеров. Наверняка можно выкинуть почти все и заменить на
assert
'ы. - Во имя иммутабельности от
createLeftChild
/createRightChild
можно избавиться. При построении дерева можно сделать не цикл с указателем, а честную рекурсию.
- Конструктор от
- Конструктор от двух детей часто применяется с двумя пустыми детьми. С частотой и без символа. Это что-то очень непонятное. И как минимум стоит распилить этот конструктор на несколько частей: один для листов, один для внутренних верших, они разные. Причём второй может ещё и частоту посчитать.
SymbolDict
std::deque<HuffmanNodePtr> getSortedUniqueSymbolNodes() const;
- это никакого отношения к таблице частот не имеет. Завязка на Хаффмана, причём ей вообще не надо быть методом.getCount
- скорееcontains
илиcount
для симметрии с STL.checkIfStringIsBitString
- это точно не метод структуры, он поля не использует.
HuffmanCompressTree
- После вызова конструктора всегда надо звать
build()
. Это плохо: снова сложный инвариант. Сделайте всё в конструкторе. Заодно исчезнут 2-3 поля и структура перестанет быть мутабельной.
- После вызова конструктора всегда надо звать
HuffmanExtractTreeException
- ровно один вид исключений, надо убрать обобщение.DECRYPT_DFS_STEP_TO_NULL
- это название про деталь реализации, а не "почему возникла ошибка". А возникла она потому что нам дали неверный код.
HuffmanExtractTree
doNextDecryptDfsStep
- так себе интерфейс, снова добавляет мутабельность и сложный инвариант внутрь дерева (которое вообще не меняется).
HuffmanArchiver
- Кажется, что поля и конструктор вообще не нужны. Это просто сборник из двух функций, даже статическими можно сделать. Я бы предложил привязываться к потокам ввода-вывода в
конструкторе, а
compress()
/decompress()
параметры не принимают и возвращаютStatistics
.
- Кажется, что поля и конструктор вообще не нужны. Это просто сборник из двух функций, даже статическими можно сделать. Я бы предложил привязываться к потокам ввода-вывода в
конструкторе, а
Стиль 5/8
- Всякие исключения лучше наследовать от
logic_error
/runtime_error
и делатьusing
конструктора. outs_.binary
- это просто в точности константаstd::ios::binary
(потому что это членenum
'а, а они видны как обычные члены. Так что этиassert
'ы бесполезны.- В реализациях оставляете пустые строки между методам (в
.h
лучше не оставлять, а группировать методы в группы). Например, вBitManips.cpp
. - НЕ ОБЪЯВЛЯЙТЕ ПЕРЕМЕННЫЕ ВНЕ ЦИКЛОВ (смотрю на
void HuffmanCompressTree
)- Аналогично не объявляйте переменные, пока они не потребутся. Например,
CLI
вmain
зачем-то определён в начале... И так в куче мест. Но это вы скорее заложник архитектуры, в которой всё мутирует, везде сложные инварианты.
- Аналогично не объявляйте переменные, пока они не потребутся. Например,
BitManips
constexpr size_t BITS_NUMBER = 8;
- лишний, лучше захардкоидть.buffIsClear
- не стоит в интерфейсе ссылаться на внутренние детали реализации ("буфер" какой-то). Лучше сказать что-то вроде "записано целое число байт"/"записано нецелое число байт":hasIncompleteByte
. Аналогично в читальщике.getWrittenBytesNumber
-->getBytesWritten
. А сейчас оно ещё неконсистентно даже с именем поля.bitToWriteIn_
-->nextBitPos_
,writtenBytesCnt_
-->bytesWritten_
clearBuff
--> скорееskipZerosUntilByteBorder
. И может скорее возвращать "удалось или нет". А после этогоcheckIfUnreadBuffBitsAreZeros
можно выпилить.- Обычно не
buff
, аbuf
. - Один байт следует записывать через
put
/get
, а неreinterpret_cast
. - Тесты
- Вместо
convertToByte
используйте битовые литералы:0b1110'0011
.- Вместо записи в
ans
байт вперемешку с тестом, лучше сравнитьss.str()
с готовым массивом/string
. Будет проще код и понятнее логика, плюс чёткое отделение Arrange-Act-Assert.
- Вместо записи в
- Вместо
CLI
constexpr size_t ARGUMENTS_NUMBER = 6;
- точно деталь реализации и должна жить в.cpp
. А ещё эта проверка не нужна: если аргументов неверное количество, то всегда можно рассказать пользователю, что именно с ними не так: дубликат, чего-то не хватает... Лучше так, чем подгонять константу "6".assert(0)
-->assert(false)
.return
послеassert
следует убрать.- Вместо
== ""
лучше.empty()
(с!=
аналогично). - Тесты
- Не надо делать
argc
руками, возьмитеstd::size()
- Не надо делать
Huffman
MAX_DIFF_BYTES_COUNT
- тоже явно деталь реализации из.cpp
- Вместо
isLabeled_
+label_
лучшеstd::optional<>
. getNodeCmp
не нужен, просто передайте лямбду напрямую. А ещё это вообще свободная функция может быть в.cpp
.assert(minNodePtr->hasLeftChild() && minNodePtr->hasRightChild()); assert(!minNodePtr->isLabeled());
- наружу просится методisLeaf()
/isInner()
.HuffmanCompressTree
/HuffmanExtractTree
- безумно странные названия. Назовите их по тому, что эти структуры делают, а не как используются. Типа: "бор" или "алгоритм Хаффмана" (но тогда не является ли это просто функцией, а не классом? Тоже надо подумать).
HuffmanArchiver
} catch (const std::exception& ex) { throw; }
- абсолютно бесполезная конструкция. Исключение и так вылетит.
comment:5 Changed 4 years ago by
Owner: | changed from Solovyev Gleb to Egor Suvorov |
---|---|
Type: | ожидаются исправления → ожидается проверка |
Version: | 2.0 → 3.0 |
Все исправил, кроме наследования исключений от std::runtime_error в местах, где были enum-ы - слишком неудобно и громоздко в данном случае.
Дедлайн третьей попытки был назначен на понедельник 11 мая 22:59.
comment:6 Changed 4 years ago by
Также вместо get и put пользуюсь read и write c reinterpret_cast, так как: работаю с unsigned char-ами, для этого нужен каст; при этом get и put от его использования не спасают (get точно, поэтому для консистенции write идет вместе с read).
проедленный промежуточный дедлайн на один день до 22:59 пятницы