Answer the question
In order to leave comments, you need to log in
I made a Code Review, maybe I made a mistake somewhere or do you have something to add?
In general, one of these days I was interviewing in one company as a middle + / senior developer and I was asked to do a Code Review. They didn't like the way I did it. Maybe it was necessary to give a more detailed description of why this is not possible, but is it possible? I suggest you take a look at the code, I commented out the problem areas in my opinion. Maybe I did something wrong? Or do you have something to add? Or did I not find everything? Please write your answer as detailed as possible.
using System;
using System.Collections.Generic;
//EXTERNAL CODE
//Приведен только для справки, никак нельзя менять, код обфусцирован, исходников нет
public sealed class DataProvider : IDisposable
{
public extern int LongRunningCalculation(int value, int value2);
public extern void Dispose();
}
// CODE FOR REVIEW:
class Class2 {
// Здесь есть какие-то ошибки тут?
private readonly object _sync = new object();
// Переменая должна быть задукументирована что это такое _ht?
// Предпологаю что нельзя использовать int[] как ключ Почему нельзя?
// Может такой словарь иметь ключ и второе значение как список или массив?
// Думаю да! Вот так Dictionary<int, List<string>>
// Памятка что значение как массив или список может быть, а может быть оно как object (boxing / unboxing)? Не класс ил strcut?
// Какой ключ ты предлогаешь использовать?
// struct (value type) лучше чем class (reference type) и поэтому если ты хочешь написать свой ключ хороший, то во первых ты должен использовать struct, это не ссылочный тип и будет работать быстрее чем класс.
private static Dictionary<int[], object> _ht;
public int GetValue(int index, int index2)
{
Init(); // Это должно быть в constructor? Вроде да! А где constructor?
// Как уже упоминал выше это плохой ключ
var key = new[] {index, index2};
// Слишком сложная логика проверки думаю что ее можно вынести в отдельный метод
if (_ht.ContainsKey(key) & _ht[key].GetType() == typeof(int))
// casting тип object to int Explicit conversions, каждый раз делать casting это хорошо? Думаю нет (плохой ключ)
return ((int)_ht[key]);
else
// Возвращается тип null а у нас int в сигнатуре метода
// По моему мнению можно сделать тип nullable в сигнатуре метода но я думаю это плохая практика
return null;
}
public void Init()
{
// Плохой стиль кода не используются if (code ...) { code ... } также и с lock
if (_ht == null) // try / catch / finaly - я думаю что lock дорогостоящая операция так ли это? (Мой вопрос)
lock (_sync)
Create();
// lock это одна из самых дешевых оперций по синхронизации (мнение Senior и Team Lead)
// Но под капотом lock - это Monitor реализован с использование try / catch / finally? (Мой вопрос)
}
public static void Create()
{
// Мне не неравиться как инициализировали DataProvider
// Нужно быть внимательным! DataProvider реализует интерфейс Dispose()
// using var provider = new DataProvider(); // some as using (var ...) {}
DataProvider provider = new DataProvider();
// Испольование констант в коде как 100 и 12 плохая практика
// Как вообще это явление называется в коде обычно (использование констант в коде) может быть bad style of code?
// Использование двух циклов обезательно или мы можем придумать что-то лучше? Но нам нужны индексы в LongRunningCalculation
for (int i = 0; i < 100; i++)
// Цикл начинается с j = 1
for (int j = 1; j <= 12; j++)
// Каждый раз создается new [] это тоже плохо (плохой ключ)
_ht[new [] { i, j }] = provider.LongRunningCalculation(i, j);
// if we used for using keyword provider will be disposed here
}
}
Answer the question
In order to leave comments, you need to log in
I will describe how I would comment
public sealed class DataProvider : IDisposable
{
// nit: Предложил бы названия firstValue, secondValue либо более осмысленные, если возможно
public extern int LongRunningCalculation(int value, int value2);
public extern void Dispose();
}
// nit: сразу бы хотелось видеть уровень доступа и sealed (если класс не планируется наследовать)
// Class2 - дать нормальное имя
// { - перенести на 2ю строку по рекомендациям code style от microsoft (если не принято иных)
class Class2 {
// Синхронизация не нужна, если убрать метод Init, а Create вызвать в статическом конструкторе
private readonly object _sync = new object();
// _ht - дать осмысленное название
// Судя по использованию, value может быть int`ом. Не зачем иметь лишний boxing и проверки на тип
// _ht статический, значит к нему могут быть обращения из разных потоков, лучше сделать его ConcurrentDictionary
// Прям сходу не могу сказать, но, возможно, использовал бы какой то другой тип Dictionary <key, key, val> (самописный или существующий), кажется, так было бы быстрее чем массив в ключе
private static Dictionary<int[], object> _ht;
// nit: хотелось бы имена со смыслом
public int GetValue(int index, int index2)
{
// Лишний метод, удалить. Create вызовем в static конструкторе
Init();
// Если ключ у нас объект, то необходимо реализовать IEqualityComparer для этого Dictionary (иначе не понятно как по нему искать)
var key = new[] {index, index2};
// Проверка на тип не нужна, Dictionary сделаем типа int
if (_ht.ContainsKey(key) & _ht[key].GetType() == typeof(int))
// приведение типов больше не нужно
return ((int)_ht[key]);
// nit: else не обязателен
else
// int не может быть null, будет ошибка, вернуть либо default, либо возвращаемое значение должно быть int?
return null;
}
// Метод удалить, вызовем Create в статическом конструкторе без lock
public void Init()
{
if (_ht == null)
lock (_sync)
Create();
}
// Нет смысла делать метод public, сделать private
public static void Create()
{
// nit: и так видно какой тип создаём, можно использовать var
// Обернуть в using
DataProvider provider = new DataProvider();
// Тут следует инициализировать значение _ht, т.к. ранее оно нигде не создаётся
// Не забыть передать реализацию IEqualityComparer в конструктор
// nit: хотелось бы видеть использование фигурных скобок (если не принят иной code style)
// nit: вместо int можно var
// i и j, похоже, несут какой то смысл, можно попробовать придумать нормальное название (иначе не понятно почему 100 и 12, их можно в константы класса)
// nit: возможно можно использовать Parallel.ForEach
for (int i = 0; i < 100; i++)
for (int j = 1; j <= 12; j++)
_ht[new [] { i, j }] = provider.LongRunningCalculation(i, j);
}
}
public interface IDataProvider : IDisposable
{
int LongRunningCalculation(int firstValue, int secondValue);
}
public sealed class DataProvider : IDataProvider
{
public extern int LongRunningCalculation(int firstValue, int secondValue);
public extern void Dispose();
}
public sealed class DataProviderService
{
public DataProviderService(IDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
private static readonly ConcurrentDictionary<int[], int?> _calculatedCache = new ConcurrentDictionary<int[], int?>(new CalculatedEqualityComparer());
private readonly IDataProvider _dataProvider;
public int? GetValue(int firstValue, int secondValue)
{
var isNotSupportedValues = firstValue > 100 || firstValue < 0 || secondValue < 1 || secondValue > 12;
if (isNotSupportedValues)
{
return null;
}
var key = new[] { firstValue, secondValue };
if (!_calculatedCache.TryGetValue(key, out var result))
{
result = _dataProvider.LongRunningCalculation(firstValue, secondValue);
_calculatedCache.TryAdd(key, result);
}
return result;
}
}
The main task of a code review is to give feedback on the work done, and not to write an essay "based on".
Your comments are not code reviews, especially for the level of candidate they are looking for.
PS Two points are not clear.
one.
public extern int LongRunningCalculation(int value, int value2);
public int GetValue(int index, int index2)
It is strange that no one said, but here is the biggest problem in the Russian language. Well, seriously, everyone can make mistakes, writing is not always correct, but this is just an unacceptably terrible text. I understand that Russian may not be a native language for a person, but then you can write in English.
Why is no one asking what class2 does anyway? That is, in order to write a good review, you need to understand the context of using this code well. Other than that, I agree with what has already been said. The code was written by a dropout student, and the review was written by a middle who failed to express his thoughts. (Sorry, nothing personal). I honestly tried to make out what I would do after reading such a review of my code, and realized that I could not understand about half. Of course, in a review, you need to explain everything in as much detail as possible and taking into account who will be reading it. If a person wrote such code, then obviously, the level of detail should be maximum. Moreover, it probably makes sense to first point out the main mistakes, let him fix them, and already at the second review, if he does not plant new ones, which is very, very likely, write about variable names and code-style.
Well, this is my approach. A particular interviewer may well not share it. Maybe they have a "galley" and the presence of even one "approach" of the reviewer is a luxury. So I can quite admit, they considered that they "flunked", because they themselves do not know how they need it.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
//EXTERNAL CODE
//Приведен только для справки, никак нельзя менять, код обфусцирован, исходников нет
public sealed class ExternalDataProvider : IDisposable
{
public extern int LongRunningCalculation(int value, int value2);
public extern void Dispose();
}
/// <summary>
/// Объект-значение
/// <para>Базовый класс</para>
/// </summary>
public abstract class ValueObject<T> where T : ValueObject<T>
{
public static bool operator ==(ValueObject<T> value1, ValueObject<T> value2)
{
if ((object)value1 == null)
return (object)value2 == null;
return value1.Equals(value2);
}
public static bool operator !=(ValueObject<T> value1, ValueObject<T> value2)
{
return !(value1 == value2);
}
public override bool Equals(object obj)
{
var valueObject = obj as T;
if (ReferenceEquals(valueObject, null))
return false;
return CompareValues(valueObject);
}
public override int GetHashCode()
{
return GetValueHashCode();
}
/// <summary>
/// Получить HashCode
/// </summary>
/// <returns></returns>
protected abstract int GetValueHashCode();
/// <summary>
/// Сравнить значения
/// </summary>
/// <returns></returns>
protected abstract bool CompareValues(T other);
}
/// <summary>
/// Параметр для DataProvider
/// </summary>
public class Parameter : ValueObject<Parameter>
{
private Parameter()
{
FirstValue = -1;
SecondValue = -1;
}
public Parameter(int firstValue, int secondValue)
{
if (firstValue > 100 || firstValue < 0 || secondValue < 1 || secondValue > 12)
throw new ArgumentException("Is not supported values");
FirstValue = firstValue;
SecondValue = secondValue;
}
/// <summary>
/// Первый параметр
/// </summary>
public int FirstValue { get; }
/// <summary>
/// Второй параметр
/// </summary>
public int SecondValue { get; }
/// <summary>
/// Default value
/// </summary>
public static readonly Parameter Empty = new Parameter();
protected override bool CompareValues(Parameter other)
{
return FirstValue == other.FirstValue && SecondValue == other.SecondValue;
}
protected override int GetValueHashCode()
{
return (FirstValue, SecondValue).GetHashCode();
}
}
/// <summary>
/// Результат для DataProvider
/// </summary>
public class Result : ValueObject<Result>
{
private Result()
{
// Default value
Value = default;
}
public Result(int value)
{
Value = value;
}
/// <summary>
/// Результат
/// </summary>
public int Value { get; }
/// <summary>
/// Default value
/// </summary>
public static readonly Result Empty = new Result();
protected override bool CompareValues(Result other)
{
return Value == other.Value;
}
protected override int GetValueHashCode()
{
return Value.GetHashCode();
}
}
/// <summary>
/// Интерфейс DataProvider
/// </summary>
public interface IDataProvider
{
public Result GetResult(Parameter parameter);
}
/// <summary>
/// Получить данные из ExternalDataProvider без кеша
/// </summary>
public class DataProvider : IDataProvider
{
public Result GetResult(Parameter parameter)
{
using var externalDataProvider = new ExternalDataProvider();
return new Result (externalDataProvider.LongRunningCalculation(parameter.FirstValue, parameter.SecondValue));
}
}
/// <summary>
/// DataProvider с кешированием данных
/// Реализация кеша не имеет принципиального значения
/// </summary>
public class CacheDataProvider : IDataProvider
{
private readonly IDataProvider dataProvider;
private readonly ConcurrentDictionary<Parameter, Result> cache = new();
public CacheDataProvider(IDataProvider dataProvider)
{
this.dataProvider = dataProvider;
}
public Result GetResult(Parameter parameter)
{
return cache.GetOrAdd(parameter, (p => dataProvider.GetResult(p)));
}
}
First things first, I'll write, thank you all for your answers!) In general, it would be good if I took into account all the answers of the developers on habr and my friends. But I decided to post this answer to my question for the time being.
It would be possible to add more documentation to this code and something else. Like so...
/// <summary>
/// Project status
/// </summary>
public enum ProjectStatus
{
NotStarted,
Active,
Completed
}
/// <summary>
/// Get project by id
/// </summary>
/// <param name="id">Id of project</param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="ProjectResponse"/></returns>
[HttpGet]
[Route("projects/{id}")]
[ProducesResponseType(typeof(ProjectResponse), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task<ProjectResponse> GetProject(long id, CancellationToken cancellationToken)
{
return await _projectService.GetByIdAsync(id, cancellationToken);
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
//EXTERNAL CODE
//Приведен только для справки, никак нельзя менять, код обфусцирован, исходников нет
public sealed class DataProvider : IDisposable
{
public int LongRunningCalculation(int value, int value2)
{
Thread.Sleep(200); // milliseconds
if ((value + value2) % 3 == 0) // 1 of 3 throws exceptions
throw new ArgumentNullException();
else
return value + value2;
}
public void Dispose() { }
}
public class Calculator : IDisposable
{
private readonly Range rangeI = 0..10;
private readonly Range rangeJ = 1..10;
private readonly IDataProvider dataProvider;
private readonly ConcurrentDictionary<Key, int> results = new();
private readonly ConcurrentDictionary<Key, Exception> exceptions = new();
public Calculator(DataProvider externProvider)
{
dataProvider = new CustomProvider(externProvider);
}
public async Task Calculate() /// async!!!
{
var tasks = new ConcurrentBag<Task>();
for (var i = 0; i < rangeI.End.Value - rangeI.Start.Value; i++)
for (var j = 0; j < rangeJ.End.Value - rangeJ.Start.Value; j++)
{
var key = new Key(i, j);
tasks.Add(Task.Run(async () =>
{
try //try-get
{
var result = await dataProvider.LongRunningCalculationAsync(key.FirstValue, key.SecondValue);
results.TryAdd(key, result);
}
catch (Exception ex) // try-get
{
exceptions.TryAdd(key, ex);
}
}));
}
await Task.WhenAll(tasks);
}
public int GetValue(int first, int second)
{
if (results.TryGetValue(new Key(first, second), out int value))
{
return value;
}
else
{
if(exceptions.TryGetValue(new Key(first, second), out Exception ex))
throw ex;
}
return 0;
}
public void Dispose() // обязательно реализовать
{
if (dataProvider != null) // проверка на null
dataProvider.Dispose();
}
}
public interface IDataProvider : IDisposable
{
Task<int> LongRunningCalculationAsync(int firstValue, int secondValue);
}
public class CustomProvider : IDataProvider
{
private readonly DataProvider _provider;
public CustomProvider(DataProvider provider)
{
_provider = provider;
}
public void Dispose()
{
if (_provider != null)
_provider.Dispose();
}
public Task<int> LongRunningCalculationAsync(int firstValue, int secondValue)
{
var tsc = new TaskCompletionSource<int>();
Task.Run(() =>
{
try
{
var result = _provider.LongRunningCalculation(firstValue, secondValue);
tsc.SetResult(result);
}
catch (Exception e)
{
tsc.SetException(e);
}
});
return tsc.Task;
// no, because Exception
return Task.Run(() => _provider.LongRunningCalculation(firstValue, secondValue));
}
}
readonly public struct Key // struct immutable
{
public readonly int FirstValue;
public readonly int SecondValue;
public Key(int first, int second)
{
FirstValue = first;
SecondValue = second;
}
public override int GetHashCode() // override !!!
{
return (FirstValue.GetHashCode() * 1_000_000 + SecondValue.GetHashCode()) % 1_000_000_000;
}
public override bool Equals(object obj) // override !!!
{
var other = (Key)obj;
return GetHashCode() == other.GetHashCode();
}
public override string ToString() // не обязательно, но поскольку у нас ключи закрытые - сделаем
{
return FirstValue.ToString() + " " + SecondValue.ToString();
}
}
class Program
{
static void Main(string[] args)
{
using var calculator = new Calculator(new DataProvider());
calculator.Calculate().Wait();
try
{
var r = calculator.GetValue(1, 1);
Console.WriteLine("1: 1: " + r);
r = calculator.GetValue(1, 2);
Console.WriteLine("1: 1: " + r);
r = calculator.GetValue(1, 3);
Console.WriteLine("1: 1: " + r);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question