Ниже — разбор четырех интересных задачек с очередного собеседования. Это задача на проведение код-ревью, на создание индексов в БД, на кэширование данных и на парсинг конфиг-файла. Рекомендуется перед обзором решений, попробовать решить представленные задачи самостоятельно, затем сравнить ваше решение с моим.
/**/
Задача 1: код-ревью
Провести код-ревью: https://gist.github.com/MaxSem/b3014252d56dc2b7bf7653633f0e9fb5
<?php class CurrencyConverter { private function connect_db ( $sql ){ $conn = mysql_connect('host', 'user', 'password'); mysql_select_db('database'); $res=mysql_query($sql,$conn); mysql_close($conn); return $res; } public function get_rate ( $currency ) { $res = $this->connect_db("SELECT rate FROM rates WHERE currency = '$currency'"); $row = mysql_fetch_array($res); return $row['rate']; } public function parse ( $input ) { $input = trim($input); $pos = strpos($input,' '); if( $pos != strrpos($input,' ')){ return array ( 'error' => 'true', 'msg' => 'invalid input: '.$input ); } return array ( 'error' => 'false', 'currency' => substr($input,0,$pos), 'amount' => substr($input,$pos+1) ); } public function set_rate ( $currency, $rate ) { $currency = strtoupper($currency); $res = $this->connect_db("SELECT rate FROM rates WHERE currency = '$currency'"); $row = mysql_fetch_array($res); if($row['rate']){ $this->connect_db("UPDATE rates SET rate=$rate WHERE currency='$currency'"); }else{ $this->connect_db("INSERT INTO rates (currency,rate) VALUES ('$currency',$rate)"); } } public function convert_to_usd ( $input ) { $result = array(); if(gettype($input) == 'string'){ $input = array( $input ); } if(gettype($input) != 'array'){ return array ( 'error' => 'true', 'msg' => 'invalid input' ); }else{ foreach( $input as $item){ $item_parsed = $this->parse($item); $rate = $this->get_rate( $item_parsed['currency']); $usd = $rate * $item_parsed['amount']; array_push($result,'USD '.$usd); } } return $result; } public function convert_from_usd ( $currency, $amount) { function walk_amount (&$item,$key,$rate){ $item = number_format($item / $rate, 2, '.', ''); } if(!$currency || !$amount){ $return = array ( 'error' => 'true', 'msg' => 'invalid input argument' ); }else{ $rate = $this->get_rate( $currency ); $amounts = explode(',',$amount); array_walk($amounts,'walk_amount',$rate); $return = array ( 'error' => 'false', 'content' => implode(',',$amounts)); } return '{"error":'.$return['error'].',"content":"'.$return['content'].'"}'; } } $converter = new CurrencyConverter; echo $converter->convert_from_usd($_GET['currency'],$_GET['amount']); // for testing each function: echo $converter->get_rate($_GET['currency']); echo $converter->set_rate($_GET['currency'],$_GET['rate']); var_dump($converter->parse($_GET['input'])); var_dump($converter->convert_to_usd('JPY 10000')); echo '<br />'; var_dump($converter->convert_to_usd(array('JPY 10000','AUD 2000','CZK 4000'))); ?>
Решение:
Основное
-
не SOLID-ный код
-
Single responsebility: разбить класс на три надо (
CurrencyConverter, CurrrencyRate, DbConnection)
-
-
соединение с БД поправить на mysqli / PDO
-
избавиться от хардкода в sql (биндинг использовать)
Все замечания (в т.ч. от нейронки Claude)
В целом код выглядит неплохо, но есть несколько замечаний:
-
Использование устаревших функций MySQL без обработки ошибок (mysql_query, mysql_fetch_array и т.д.). Лучше использовать PDO или mysqli с проверкой результатов запросов.
-
Отсутствует валидация и экранирование входных данных перед запросами к БД. Это создает уязвимости для SQL-инъекций.
-
Нет обработки ошибок при получении данных из БД.
-
Функция connect_db вызывается в каждом методе — лучше сделать подключение один раз при создании объекта.
-
Желательно вынести работу с БД в отдельный класс для лучшего разделения логики.
-
Имена методов и переменных не соответствуют стандартам именования в PHP (например, convert_to_usd вместо convertToUsd).
-
В методах отсутствуют phpdoc комментарии с описанием параметров и возвращаемых значений.
-
В целом класс довольно большой, лучше разбить на несколько классов по назначению.
Рекомендую:
-
Использовать PDO или mysqli для работы с БД
-
Добавить валидацию и экранирование входных данных
-
Обрабатывать ошибки базы данных
-
Вынести работу с БД в отдельный класс
-
Разбить класс на несколько по назначению
-
Добавить phpdoc комментарии
-
Придерживаться стандартов именования в PHP
Это поможет сделать код более безопасным, структурированным и поддерживаемым.
Задача 2: Индексы в базах данных
CREATE TABLE x ( a INT, b INT, INDEX(a, b) );
-
Привести примеры, когда будет использоваться индекс?
-
Что будет если поменять ключи местами?
Решение
SELECT * FROM x WHERE a = 1 AND b = 2; -- да, тут будет использоваться -- будет ли использован индекс тут? -- не будет использоваться SELECT * FROM x WHERE a > 1 AND b = 2 -- будет ли использован индекс тут? -- будет использоваться SELECT * FROM x WHERE a = 1 AND b > 2
Задача 3: кэширование данных
В существующем коде есть ограничения на редактирование базовых классов.
Как добавить кэширование сюда, чтобы не запускать метод slow()
каждый раз?
(с учетом ограничений на редактирование классов)
Решение
// Can't change interface MyInterface { public function slow(): SomeData; } // Can't change class MyClass implements MyInterface { public function slow(): SomeData { ... } // needs caching } // Can't change class Receiver { public function __construct(MyInterface $my) { … } function f() { $data = $this->my->slow(); ... } } $receiver = new Receiver(new MyClass()); $receiver->f();
Заметка П.: можно использовать паттерн Wrapper, Proxy, Adapter
Решение 1 (не идеальное)
class ReceiverChild extends Receiver { public function __construct(MyInterface $my) { } function f() { if(!$this->isCached()) { $data = $this->my->slow(); $this->saveToCache($data); } else { $data = $this->getFromCache(); } // .... extra code from Receiver->f() return $data; } }
плохое решение: т.к. в методе Receiver->f()
может быть много логики и не надо ее копи-пастить тут (переопределяя этот метод)
Решение 2 (верное)
Создаем потомка MyClass где пишем нужную нам логику
class MyClassChild implements MyInterface { public function slow(): SomeData { if(!$this->isCached()) { $data = $this->my->slow(); $this->saveToCache($data); } else { $data = $this->getFromCache(); } return $data; } }
Задача 4: парсер ini файла
Написать парсер ini-файла где есть сабсекции внутри секций, пример ниже:
/* Example input: foo=aaa [bar] baz = bbb [bar/quux] frob = ccc Should result in the following output: [ ‘foo’ => ‘aaa’, ‘bar’ => [ ‘baz’ => ‘bbb’, ‘quux’ => [ ‘frob’ => ‘ccc’, ], ], ] */ function parse(string $filename): array { }
Написать свой парсер конфигов с указанными вложенными секциями
есть готовая ф-я parse_ini_file($filename, true);
но она не умеет парсить сабсекции вида sec1/sec2/sec3
как в примере
function parse(string $filename): array { $res = parse_ini_file($filename, true); // var_dump($res); if(is_array($res) && count($res)) { foreach($res as $k => &$v) { if(strstr($k, '/') !== false) { $sub = explode('/', $k); foreach($sub as $subName) { $v[$subName] = $v[$k]; } } } } }
Решение 1 (не идеальное)
Парсим файл базовой функцией parse_ini_file
далее разбираем имена секций, ищем в них составные и обрабатываем их (создаем массивы с саб-секциями в результате).
function parse(string $filename): array { $res = parse_ini_file($filename, true); // var_dump($res); if(is_array($res) && count($res)) { foreach($res as $k => &$v) { if(strstr($k, '/') !== false) { $sub = explode('/', $k); foreach($sub as $subName) { processSubsection($subName, $v); } } } } } function processSubsection($subName, &$data) { // $v[$subName] = $v[$k]; $data[$subName] = $data; // .... process child sections above }
не будет работать для секций с 3го уровня и далее , нужно доработать…
Решение 2 (финальное, рабочее)
Будем обрабатывать подсекции в цикле.
<?php /** * Parse ini file with subsections * @param string $filename * @return array */ function parse(string $filename): array { $res = parse_ini_file($filename, true); if(is_array($res) && count($res)) { foreach($res as $k => &$data) { if(strstr($k, '/') !== false) { $subNames = explode('/', $k); // make subsections inside current section $subsec = &$res; foreach($subNames as $subName) { $subsec = &$subsec[$subName]; } $subsec = $data; // delete original section with composite key unset($res[$k]); } } } return $res; } $data = parse('config.ini'); print_r($data);
Ввод (файл)
foo=aaa [bar] baz = bbb [bar/quux] frob = ccc
Вывод
Array ( [foo] => aaa [bar] => Array ( [baz] => bbb [quux] => Array ( [frob] => ccc ) ) )
PS: рекомендую курсы Отус для начинающих и продвинутых разработчиков на языке PHP, после которых данные задачки вы сможете решать “на лету“. Также рекомендую скачаь мою книгу по подготовке к собеседованиям веб-разработчика. Здесь подробно разобрано множество вопросов и задач с интервью, накопленных мной за 10+ лет опыта.
Нет Ответов