Ниже — разбор четырех интересных задачек с очередного собеседования. Это задача на проведение код-ревью, на создание индексов в БД, на кэширование данных и на парсинг конфиг-файла. Рекомендуется перед обзором решений, попробовать решить представленные задачи самостоятельно, затем сравнить ваше решение с моим.
/**/
Задача 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+ лет опыта.

Нет Ответов