Ще одне цікаве завдання, з тих, що пропонує Яндекс:
Есть класс CodeGenerator, который умеет генерить код на разных языках.
- class CodeGenerator
- {
- public:
- enum Lang {JAVA, C_PLUS_PLUS, PHP};
- CodeGenerator(Lang language) { _language=language; }
- std::string generateCode()
- {
- switch(_language) {
- case JAVA: //return generated java code
- case C_PLUS_PLUS: //return generated C++ code
- case PHP: //return generated PHP code
- }
- throw new std::logic_error("Bad language");
- }
- std::string someCodeRelatedThing() // used in generateCode()
- {
- switch(_language) {
- case JAVA: //return generated java-related stuff
- case C_PLUS_PLUS: //return generated C++-related stuff
- case PHP: //return generated PHP-related stuff
- }
- throw new std::logic_error("Bad language");
- }
- private:
- Lang _language;
- }
Исходя из предположения, что количество языков будет добавляться, предложите refactoring кода. Аргументируйте преимущество вашего кода над существующим.
Слово “рефакторінг” недарма виділено. Практика показала, що його не знають не те що більшість програмістів, а навіть тімліди з десятилітнім досвідом роботи. У вікіпедії сказано, що рефакторінг — процес зміни коду без зміни його функціональності. Прямо протилежно гаслу “працює? не чіпай ні в якому разі”. Але чіпати не те що треба, а просто необхідно, бо навіть працюючий код може бути гнилим, тобто містити недоліки проектування. А чому полягають ці недоліки, ми зараз і розглянемо.
Очевидно, основною проблемою є те, що для додавання нової мови доведеться правити код у двох місцях. В реальному прикладі клас може займати тисячу рядків, знайти потрібну частину коду буде важкувато.
Але навіть якби оператор switch використовувався б лише один раз, все одно його слід було б замінити. Правильне використання такого оператору — коли в ньому не більше 8-10 альтернатив, на кожну з яких не більше 5-6 рядків. Інакше стуктура стає громіздкою і розібратися в ній надзвичайно важко. Приклад (цей і наступні лістинги особисто мої)
- int someUgleFunc()
- {
- int someVar;
- < тут якісь початкові дії, які вас цікавлять >
- if (< якась умова>)
- {
- < ще початкові дії>
- switch (someVar)
- {
- case 1:
- < тут багато-багато рядків коду>
- break;
- case 2:
- < тут ще 20-30 рядків коду>
- break;
- case 3:
- < а це місце теж вас цікавить >
- break;
- }
- }
- }
Уявіть, що ви маєте виконати якусь зміну у частині case 3. При тому, що до цього часу ви коду не бачили, тож треба розібратися, що робиться на початку функції, а що всередині switch. Виходить, між двома ділянками програми, які треба аналізувати, знаходиться кількадесят рядків стороннього (на даний момент) коду. Звісно, це надзвичайно незручно, весь час туди-сюди скролити.
Ще один недолік — при стандартному відступі в чотири пробіли основний код опиняється на шістнадцять пробілів від краю. Неестетично.
Тепер, як це все подолати. Мартін Фаулер (автор слова “рефакторінг”) у своїй книзі рекомендує прийом “заміна поліморфізмом оператора умови”( “replace conditional with polymorphizm”). Ще тут використовується паттерн “фабрика” (Factory). Багато красивих слів, але їх варто використовувати, вони мають реальне значення. Покращений приклад:
- enum Lang {JAVA, C_PLUS_PLUS, PHP};
- class BaseCodeGenerator
- {
- public:
- BaseCodeGenerator() {};
- virtual ~BaseCodeGenerator() {} ;
- virtual std::string someCodeRelatedThing()=0;
- virtual std::string generateCode() = 0 ;
- }
- class JavaCodeGenerator
- {
- public:
- JavaCodeGenerator() ;
- ~JavaCodeGenerator() ;
- }
- class CppCodeGenerator
- {
- public:
- CppCodeGenerator() ;
- ~CppCodeGenerator() ;
- }
- class PhpCodeGenerator
- {
- public:
- PhpCodeGenerator() ;
- ~PhpCodeGenerator() ;
- }
- class CodeGeneratorFactory
- {
- public:
- static BaseCodeGenerator* getGenerator(Lang lang)
- {
- switch(lang) {
- case JAVA:
- return new JavaCodeGenerator;
- case C_PLUS_PLUS:
- return new CppCodeGenerator;
- case PHP:
- return new PhpCodeGenerator;
- default:
- throw new std::logic_error("Bad language");
- }
- }
- }
Вах, тут варто зупинитися. Зрозуміло, що використовується це якось так:
- std::string someJavaCode;
- …
- BaseCodeGenerator* gen = CodeGeneratorFactory::getGenerator(JAVA);
- someJavaCode = gen->generateCode();
Можливі два варіанти. Якщо клас CodeGenerator – частина SDK і ним користуються багато сторонніх програмістів, звісно, що ми не можемо його змінити. Доведеться загортати описаний вище код у стандартну обгортку. В іншому випадку, коли CodeGenerator є частиною нашого особистого проєкту і використовується лише в парі місць, доцільніше буде його просто прибрати, і надалі використовувати власне фабрику.
Уважний читач міг помітити, що switch нікуди не зник, а просто сховався поглибше у CodeGeneratorFactory. Так і є, але це ж зовсім інший switch. Він гарний, білий і пухнастий
Варіанти займають по одному рядку кожен, все зрозуміло і легко читається.
Головне — ми виділили специфічний для кожної з мов код в окремий модуль (клас). Тепер, для того, щоб додати ще один генератор, не доведеться перелопачувати існуючий код. Досить буде лише додати рядок у фабриці (і, звісно, написати клас з відповідними методами).
Звісно, перше, що спадає на думку, зробити шось типу


Питання про людиноподібних роботів, що стоять на сторожі державних кордонів стає все менш смішним. Цілком можливо, що років через п’ять-десять в армії США буде служити досить багато всякорізних механічних створінь. Принаймі, Пентагон активно працює в цьому напрямі. Цього тижня, наприклад, було оголошено конкурс на розробку так званої 