Меня давно интересует тема комментирования кода. Этот пост подвиг меня формализовать свою идеологию документирования кода.
С одной стороны комментарии это типа правильно, с другой я не вижу в них особого смысла. Более того, те книжки по программированию которые я читал, как-то опускают этот момент или по крайней мере не придают комментариям большого значения. Я хочу изложить свои соображения, почему нет смыла описывать все описания параметров функций, методов и свойств классов и т.д.
Слова «описывать все описания » я написал неслучайно, ведь это почти то же что и
Когда я был маленьким, я учился программировать на Quick Basic и REXX. Я не брезгал называть переменные a, aa, x1. Потом ко мне пришла ясность, что это определенно плохая идея, потому что на следующий день ничего не понятно. Я стал называть переменные чуть более осмысленно, например money, answer, file_count. В определенный момент я решил напрограммировать punto switcher для OS/2. Естественно, нужно было изучать API и тут я как следует проникся духом самодокументирующегося кода. Названия переменных и имена функций в API OS/2 настолько проработаны, что порой даже не надо было читать описания. Было достаточно увидеть как они обозначены параметры. Мне пришла в голову мысль, почему бы не перенести всю информацию из комментариев прямо в код?
Это ключевой вопрос в данном посте. Я вижу два достаточно независимых применения. Во-первых нужно как-то понимать, что делают те или иные элементы, чтобы через некоторое время продолжить работу или исправить ошибки. Причем это касается и совместной разработки, когда коллеги должны понимать что и как. Другое применение — сообщить пользователям библиотек, как следует использовать экспортируемые символы, классы, шаблоны.
Я неспешно двигаю проект в свободное от работы время, под настроение. Перерывы в разработке бывают больше месяца, а объем приличный. У меня не возникает проблем с рефакторингом некоторых частей, потому что в коде достаточно информации. Если называть переменные и функции непонятно, даже с комментариями будет сложно разобраться что происходит.
Знакома такая ситуация? Надеюсь что нет, потому что совсем нетрудно обходить цикл по uiRowCount и uiColCount. Но в таком случае пропадает смысл в описании этих переменных.
Есть утверждение, что в самодокументирующий код нужно разбивать на большое количество функций. В таком коде сложно разобраться и исполнение замедляется. Лучше развернуть старую добрую портянку, приправить ее комментариями и читать как роман Толстого.
Старая портянка — весьма сомнительное удовольствие. Дело не в читаемости, а в простоте переработки и исправления ошибок. К тому же, факторизованный код как раз лучше в смысле читаемости. Функции — следующий уровень детализации алгоритма. Из названия должно быть ясно, что они делают. Обычно нет необходимости изучать алгоритм во всех подробностях. А если интересует какой-то момент, можно и посмотреть тело функции. Много функций замедляет исполнение? Используйте inline. В то же время, у меня есть функции более чем на сто строк и проблем с читаемостью не возникает.
Несомненно нужно писать документацию к библиотечным функциям. С точки зрения пользователя лучше, чтобы документация была в отдельном файле. Зачем при этом еще и комментировать весь код? Все нюансы необходимо вынести в описание библиотеки, если конечно нет садистской подоплеки. Я не раз сталкивался с намеком в описании «ну глянь в коде, там все есть.» В действительности это означает, что помимо RPM с библиотекой, нужно скачать исходники. Нужно найти функцию и прочитать комментарии в хедере и в самом коде. Для следующей функции то же самое. grep -R еще ни кто не отменял.
Малоприятная ситуация, не так ли? В то же время, отдельный файл будет распространяться с бинарниками и пользователь только скажет спасибо. Кстати, то же самое касается и описания архитектуры или иерархии классов. Если есть сложные цепочки, почему бы не описать их в отдельном файле?
UPD
С одной стороны комментарии это типа правильно, с другой я не вижу в них особого смысла. Более того, те книжки по программированию которые я читал, как-то опускают этот момент или по крайней мере не придают комментариям большого значения. Я хочу изложить свои соображения, почему нет смыла описывать все описания параметров функций, методов и свойств классов и т.д.
Слова «описывать все описания » я написал неслучайно, ведь это почти то же что и
std::map<std::string, Person*>mapPPersonsNames //map person pointers by names
Как я дошел до такой еретической жизни?
Когда я был маленьким, я учился программировать на Quick Basic и REXX. Я не брезгал называть переменные a, aa, x1. Потом ко мне пришла ясность, что это определенно плохая идея, потому что на следующий день ничего не понятно. Я стал называть переменные чуть более осмысленно, например money, answer, file_count. В определенный момент я решил напрограммировать punto switcher для OS/2. Естественно, нужно было изучать API и тут я как следует проникся духом самодокументирующегося кода. Названия переменных и имена функций в API OS/2 настолько проработаны, что порой даже не надо было читать описания. Было достаточно увидеть как они обозначены параметры. Мне пришла в голову мысль, почему бы не перенести всю информацию из комментариев прямо в код?
Зачем нужны комментарии в коде?
Это ключевой вопрос в данном посте. Я вижу два достаточно независимых применения. Во-первых нужно как-то понимать, что делают те или иные элементы, чтобы через некоторое время продолжить работу или исправить ошибки. Причем это касается и совместной разработки, когда коллеги должны понимать что и как. Другое применение — сообщить пользователям библиотек, как следует использовать экспортируемые символы, классы, шаблоны.
Я пишу самодокументирующийся код, потому что не хочу тратить время на двойную работу.
Я неспешно двигаю проект в свободное от работы время, под настроение. Перерывы в разработке бывают больше месяца, а объем приличный. У меня не возникает проблем с рефакторингом некоторых частей, потому что в коде достаточно информации. Если называть переменные и функции непонятно, даже с комментариями будет сложно разобраться что происходит.
… здесь надо бы сделать счетчиком цикла i_1, ведь во внешнем цикле i…
Проклятье! i_1 это не по строкам а по столбцам...
Знакома такая ситуация? Надеюсь что нет, потому что совсем нетрудно обходить цикл по uiRowCount и uiColCount. Но в таком случае пропадает смысл в описании этих переменных.
Есть утверждение, что в самодокументирующий код нужно разбивать на большое количество функций. В таком коде сложно разобраться и исполнение замедляется. Лучше развернуть старую добрую портянку, приправить ее комментариями и читать как роман Толстого.
Старая портянка — весьма сомнительное удовольствие. Дело не в читаемости, а в простоте переработки и исправления ошибок. К тому же, факторизованный код как раз лучше в смысле читаемости. Функции — следующий уровень детализации алгоритма. Из названия должно быть ясно, что они делают. Обычно нет необходимости изучать алгоритм во всех подробностях. А если интересует какой-то момент, можно и посмотреть тело функции. Много функций замедляет исполнение? Используйте inline. В то же время, у меня есть функции более чем на сто строк и проблем с читаемостью не возникает.
А как же библиотеки?
Несомненно нужно писать документацию к библиотечным функциям. С точки зрения пользователя лучше, чтобы документация была в отдельном файле. Зачем при этом еще и комментировать весь код? Все нюансы необходимо вынести в описание библиотеки, если конечно нет садистской подоплеки. Я не раз сталкивался с намеком в описании «ну глянь в коде, там все есть.» В действительности это означает, что помимо RPM с библиотекой, нужно скачать исходники. Нужно найти функцию и прочитать комментарии в хедере и в самом коде. Для следующей функции то же самое. grep -R еще ни кто не отменял.
Малоприятная ситуация, не так ли? В то же время, отдельный файл будет распространяться с бинарниками и пользователь только скажет спасибо. Кстати, то же самое касается и описания архитектуры или иерархии классов. Если есть сложные цепочки, почему бы не описать их в отдельном файле?
UPD
по просьбам сообщества привожу пример кода.
struct HGenStuff: public std::unary_function<const std::pair<wxString,int>&, void>{
std::map<wxString, DevDesc*>& mapDevs;
std::map<wxString, LogDesc*>& mapLogs;
HandlerLibData & hData;
HGenStuff(std::map<wxString, DevDesc*>&_mapDevs, std::map<wxString, LogDesc*>&_mapLogs,HandlerLibData&_hData):
mapDevs(_mapDevs), mapLogs(_mapLogs), hData(_hData) {};
inline void operator()(const std::pair<wxString,TypeFlag>&paInp){
if(paInp.second.isSet(HandlerLibData::DEVICE)){
std::map<std::string,DevInterface*>::iterator itDev;
if((itDev = hData.HLI.mapDevs.find(std::string(paInp.first.mb_str())))!=hData.HLI.mapDevs.end()){
mapDevs.insert(std::make_pair(paInp.first, new DevDesc(itDev->second, &hData)));
}
}
if(paInp.second.isSet(HandlerLibData::LOGGER)){
std::map<std::string,Logger *>::iterator itLog;
if((itLog = hData.HLI.mapLogs.find(std::string(paInp.first.mb_str())))!=hData.HLI.mapLogs.end()){
mapLogs.insert(std::make_pair(paInp.first, new LogDesc(itLog->second, &hData)));
}
}
}
};
struct HandlerActivator: public std::unary_function<HandlerLibData&, void>{
HandlerBroker *pHB;
std::map<wxString, DevDesc*>mapDevs;
std::map<wxString, LogDesc*>mapLogs;
std::map<wxString,std::map<wxString,TypeFlag> > &toUse;
bool &bSuccess;
HandlerActivator(HandlerBroker *_pHB, std::map<wxString,std::map<wxString,TypeFlag> > &_toUse, bool &_bSuccess):pHB(_pHB), toUse(_toUse), bSuccess(_bSuccess){ };
inline void operator()(HandlerLibData&inp){
std::map<wxString,std::map<wxString,TypeFlag> >::iterator it(toUse.find(inp.md5));
if(it != toUse.end())
std::for_each(it->second.begin(),it->second.end(),HGenStuff(mapDevs,mapLogs , inp));
}
~HandlerActivator(){
if (!pHB->checkLock(mapDevs))
bSuccess = wxYES == wxMessageBox(wxT("Some locks seam to be incompatible. Shall we use new device handlers selection?\n"),wxT("Confirm"), wxYES_NO | wxICON_EXCLAMATION );
if(bSuccess ) pHB->setAvailHandlers(mapDevs, mapLogs);
};
};
bool MyFrame::pocessHandlers(std::map<wxString, std::map<wxString,TypeFlag> > &mapHandlersToUse){
std::set<HandlerLibData>::iterator it = strIntConf.setHandlerLibs.begin();
std::set<HandlerLibData>::iterator itEnd = strIntConf.setHandlerLibs.end();
bool bSuccess = true;
HandlerActivator hGen(fGetHBroker(),mapHandlersToUse, bSuccess);
while(it != itEnd)
hGen(*const_cast<HandlerLibData*>(&(*it++)));
return bSuccess;
}
void MyFrame::loadHandlers(){
wxDynamicLibrary dllDev;
wxDir dirHandlers(strIntConf.sHandlersDir);
wxString sLibName;
if (!dirHandlers.GetFirst( &sLibName, wxEmptyString, wxDIR_FILES)) return;
do {
MD5 md5Op;
wxString sFullPath = strIntConf.sHandlersDir + wxFileName::GetPathSeparator() +sLibName;
wxString md5 = wxString(md5Op.digestFile((sFullPath).mb_str(wxConvLibc)),wxConvUTF8 );
if (strIntConf.setHandlerLibs.find(HandlerLibData(md5))!=strIntConf.setHandlerLibs.end()) continue;
if(!dllDev.Load( sFullPath ))
wxLogError(wxString::Format(wxT("Error Loading ")) + sLibName);
else{
void (*dynLoad)(HandlerLibInterface *) = reinterpret_cast<void (*)(HandlerLibInterface* )>(dllDev.GetSymbol(wxT("dynLoad")));
if(dynLoad) {
HandlerLibInterface HLI;
dynLoad(&HLI);
strIntConf.setHandlerLibs.insert( HandlerLibData( md5, sLibName,
strIntConf.sHandlersDir,
HLI,dllDev.Detach()));
}else dllDev.Unload();
}
}while(dirHandlers.GetNext( &sLibName));
}