По жизни получилось, что я около года не программировал. Пару месяцев назад вернулся к этому занятию. Оказалось, что руки-то еще помнят. Хотя мозги на начальственный лад очень сильно перестроились и думаю я теперь несколько иначе. Что приводит к интересным эффектам. Например, перелопачивая тоны старого кода (как своего, так и чужого), быстро стали формироваться некоторые внешние признаки, которые прозрачно намекают на наличие проблем. Причем поначалу смотришь в код, вроде все нормально. Но когда копнешь поглубже, то говнище, оказывается еще то. И как раз описанные ниже запахи признаки для меня теперь являются указанием на необходимость более тщательного и внимательного разбирательства с кодом.
Итак, пойдем по порядку возрастания потенциальной опасности. Т.е. сначала описываются самые безобидные намеки, а в конце те, которые наверняка связаны с серьезными проблемами.
Непоследовательность в оформлении кода. Обычно программист пишет в каком-то устоявшемся стиле. Не важно, нравится ли он мне или нет, но если стиль устоялся, то в нем прослеживается строгая последовательность и, временами, логичность ;) Но когда эта последовательность нарушается, то это плохой знак. Например, обычно разработчики оформляют передачу аргументов в функцию в каком-то одном стиле. Скажем, как-то так:
layer_handler_info_t
layer_handler_manipulator_t::query_layer_handler_info() const
{
return layer_handler_info_t(
m_ref_dll->query_file(),
query_registration_name(),
m_used_cfg_name,
state_to_string( m_reg_state ),
m_reg_state );
}
|
Однако, если этот стиль нарушается не понятным для меня образом:
layer_handler_info_t
layer_handler_manipulator_t::query_layer_handler_info() const
{
return layer_handler_info_t(
m_ref_dll->query_file (),
query_registration_name() , m_used_cfg_name,
state_to_string(m_reg_state ),
m_reg_state);
}
|
то это заставляет насторожиться. Конкретно в этом примере: почему-то перед скобками при вызове query_file поставлен пробел, которого нет в других случаях; два аргумента указано на одной строке, хотя другие аргументы размещаются каждый на своей строке; почему-то после query_registration_name() перед запятой стоит пробел; перед аргументом m_reg_state функции state_to_string нет пробела, а после, почему-то, есть.
Все это, конечно, смахивает на grammar nazy ;) Но для меня это признак того, что над кодом либо работали разные люди (и тогда есть шанс, что кто-то чего-то мог не знать), либо же код правился в очень большой спешке (что чревато наличием глупых, но труднообнаруживаемых ошибок).
Выполнение одних и тех действий разными способами. Например, пусть в C++ есть класс, который обеспечивает защиту от многопоточности захватом в начале каждого публичного метода специального мутекса. Если в части методов этот мутекс захватывается одной конструкцией, а в другой части методов иной конструкцией, то это подозрительно. Например:
void
task_queue_t::push( ref_task_t && task )
{
ACE_Guard< ACE_Thread_Mutex > lock( m_queue_mutex );
...
}
void
task_queue_t::push( task_vector_t && task_vector )
{
ACE_GUARD( ACE_Thread_Mutex, lock, m_queue_mutex );
...
}
bool
task_queue_t::pop( task_vector_t & task_vector )
{
ACE_Guard<ACE_Thread_Mutex> lock( m_queue_mutex );
...
}
|
Настороженность вызывает тот факт, что в одном из push-ей вместо явного объявления guard-переменной зачем-то использован макрос. Почему? Может макрос делает что-то большее, что не может сделать обычная переменная и для этого варианта push-а это очень важно. Но раз это так важно в одном push-е, то почему макрос не используется во втором push-е?
Наличие кусков закомментированного кода. Особенно если не описаны причины того, почему этот код закомментирован, но оставлен в исходнике. Т.е. когда я вижу что-то вроде:
void
task_processor_t::do_dereg_coop(
const std::string & coop_name )
{
ACE_Guard< ACE_Thread_Mutex > guard( m_lock );
std::string error_desc;
/*
m_logger->on_dereg_coop(
false,
coop_name,
START_OPERATION_STAGE,
error_desc );
*/
try
{
initiate_coop_dereg_operation( coop_name );
}
catch( const std::exception & x )
{
error_desc = x.what();
}
/*
m_logger->on_dereg_coop(
false,
coop_name,
FINISH_OPERATION_STAGE,
error_desc );
*/
}
|
То начинаю очень сильно подозревать, что код кем-то был не доведен до ума. Но сколько еще осталось сделать (и что именно еще осталось сделать), все это мне неизвестно.
Несоответствие комментариев и кода. Тут все очевидно, если я вижу комментарий, который расходится с последующим кодом, то значит, что в Интернетах кто-то неправ :)
//! Класс для добавления слоев в SObjectizer.
class SO_SYSCONF_4_TYPE layer_handler_t : public cpp_util_2::nocopy_t
{
public :
layer_handler_t(
//! Имя кооперации.
const std::string & layer_name );
virtual ~layer_handler_t();
//! Получить имя слоя.
/*!
\return Полученное в конструкторе имя кооперации.
*/
const std::string &
query_name() const;
//! Добавить слой в SObjectizer.
virtual void
add(
//! SObjectizer Environment с которым нужно работать.
so_5::rt::so_environment_t & env,
//! Конфигурационный файл для кооперации.
const std::string & cfg_file ) = 0;
private :
//! Имя кооперации.
const std::string m_layer_name;
};
|
Проблема с приведенным выше кодом в том, что "слой" (layer) и "кооперация" (cooperation) -- это разные понятия. Поэтому, когда применительно к слою употребляется термин "кооперация", то это свидетельствует о копипасте. Возможно, выполненной плохо и, поэтому, можно ждать неприятностей.
Наличие мертвого кода. Когда обнаруживаются функции/методы или атрибуты класса, которые нигде в коде не используются, это очень подозрительно. Как такое могло случится? Оставлены ли они были по недосмотру? Или же по какой-то причине оказались не задействованны, хотя должны были бы? И если они по ошибке не задействованны, то каких последствий можно ожидать?
Очень большие по объему функции/методы. Ну это классика. Чем больше кода в функции/методе, тем больше шансов, что какая-то ситуация в функции не обработана.
Большое количество похожих друг на друга фрагментов в пределах видимости. Например, несколько очень похожих друг на друга методов одного класса. Или несколько очень похожих друг на друга классов. Или даже последовательность очень похожих друг на друга действий в одной большой функции. Опять же свидетельствует о копипасте или о недостаточном уровне проектирования (не использовании имеющихся в наличии средств обобщения).
Мой последний опыт практически 100% указывает, что за внешне рабочим кодом, в котором встречается что-то из перечисленного (особенно, когда там есть все из вышеуказанного), скрываются дефекты разной степени серьезности.
Полагаю, что все это говорит о недостаточно тщательном отношении к коду. Т.е. разработчику просто было не до того, чтобы спокойно заниматься кодом и аккуратно доводить его до ума. Более того, зная, в каких условиях создавались куски, примеры из которых я (утрируя и преувеличивая) преобразовывал в показанные выше иллюстрации, я уверен, что все это следствие сильно сжатых сроков и административного давления на разработчиков. Но такие условия, к сожалению, ведут не только к наличию тупых ошибок в коде (вроде забытого присваивания или не закрытого вовремя файла). Гораздо хуже, что такие условия провоцируют серьезные архитектурые или алгоритмические ошибки. Поэтому погружение в неряшливо оформленный код заставляет меня выискивать и, к сожалению, находить более серьезные и опасные дефекты.
Такие дела. Может кому-то это поможет внимательнее относиться к написанию своего или сопровождению чужого кода.