Pull to refresh

Здравствуйте, меня зовут SiTLar, мне 30 лет и я пишу самодокументирующийся код

Reading time 5 min
Views 8.4K
Меня давно интересует тема комментирования кода. Этот пост подвиг меня формализовать свою идеологию документирования кода.

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

Слова «описывать все описания » я написал неслучайно, ведь это почти то же что и
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));
}

Tags:
Hubs:
-19
Comments 17
Comments Comments 17

Articles