Сделайте ревью кода

196
24 апреля 2019, 15:40

Сделал рефакторинг. Какая работа была проведена:

  1. разделил на методы, вынес многие в BaseManager

  2. упростил названия переменных

  3. перенёс все try-catch в базовый класс BaseManager

  4. сгруппировал методы по доступу (вверху private, далее publiс)

  5. было:

    protected RestClient GetApiClient()
    {
        var client = new RestClient
        {
            BaseUrl = new Uri(_baseApiServer)
        };
        return client;
    }
    

    стало:

    protected RestClient ApiClient
    {
        get
        { 
            return new RestClient
            {
                BaseUrl = new Uri(_baseApiServer)
            };
        }
    }
    
  6. Было

    private async Task<string> GetObjVal(string groupId)
    

    стало:

    private async Task<JSON> GetObjValue(string groupId)
    

    теперь видно, что метод возвращает JSON. Для это пришлось ввести класс JSON, а в нём поле Value

  7. Вынес константы:

    private const string getFormsAPI = "anketa.list";
    private const string getFormAPI = "anketa.get";
    private const string requestName = "request";
    private const string idKey = "id";
    private static class API
    {
        public static string GetForms { get { return "anketa.list"; } } // ! const бессмысленно? Раз отсутствует set
        public static string GetForm = "anketa.get"; 
    }
    

    мотив - чтобы избавится от префикса API в константах.

  8. Как лучше?

    Parameter request = ....
    

    или лучше

    Parameter requestParam
    

    или

    requestP
    

    чтобы было понятно, что это параметр. Или по типу можно понять, наведя мышкой?

  9. В UserName.cs я бросаю исключение, как мне посоветовали. Но везде по коду ошибки пишутся в лог. Согласно стилю предыдущего кода мне тоже надо в лог писать?

  10. JSON.cs, RootObject.cs, UserName.cs поместил в папку Model

  11. Избавился от dynamic

Если какие-то пункты некорректны, исправьте, пожалуйста. + напишите что ещё исправить

public class BaseManager
{
    private static readonly string _baseApiServer = "https://api.sendsay.ru/clu206"; // ! подчёркивания
    protected const string getGroupsAPI = "group.list";
    protected readonly IApiConfig ApiConfig;
    protected readonly ILogger Logger;
    protected CancellationToken Token { get { return new CancellationTokenSource().Token; } }
    public BaseManager(IApiConfig config)
    {
        ApiConfig = config;
        Logger = config.Logger;
    }
    protected IRestRequest GetBaseRequest(string action)
    {
        IRestRequest request = new RestRequest(_baseApiServer, Method.POST);
        request.AddParameter("apiversion", "100");
        request.AddParameter("json", "1");
        UserName username = new UserName(ApiConfig.UserName);
        string json = JsonConvert.SerializeObject(
                      new
                      {
                          one_time_auth = new
                          {
                              login = username.Login,
                              sublogin = username.Sublogin,
                              passwd = ApiConfig.Password
                          },
                          action = action
                      });
        request.AddParameter("request", json);
        return request;
    }
    protected RestClient ApiClient
    {
        get
        { 
            return new RestClient
            {
                BaseUrl = new Uri(_baseApiServer)
            };
        }
    }
    protected string AddObjectToParam(Parameter param, string key, string value)
    {
        value = key == "obj" ? value : '"'+value+'"';
        string paramVal = param.Value.ToString();
        return paramVal.Insert(paramVal.Length - 1, ",\"" + key + "\":" + value + "");
    }
    protected async Task<IRestResponse> GetResponseAsync(IRestRequest request)
    {
        IRestResponse response = null; // !
        try
        {
            response = await ApiClient.ExecuteTaskAsync(request, Token);
            return response;
        }
        catch (Exception e)
        {
            Logger?.Error($"Response getting error: {e.Message}");
        }
        if (response.StatusCode != HttpStatusCode.Created && response.StatusCode != HttpStatusCode.OK)
        {
            Logger?.Error($"Server return error: {response.StatusCode}");
        }
        return null;
    }
    protected T GetDeserialized<T>(string content)
    {
        try
        {
            return JsonConvert.DeserializeObject<T>(content);
        }
        catch (Exception e)
        {
            Logger?.Error($"Deserialization error: {e.Message}. Probably API structure has changed");
        }
        return (T)(object)null; // !
    }
}


    public class SubscriberManager : BaseManager, ISubscriberManager
    {
        private FieldManager fieldMng;
        private const string getMembersAPI = "member.set";
        private const string emailKey = "email";
        private const string newbieConfirmKey = "newbie.confirm";
        private const string newbieLetterConfirmKey = 
             "newbie.letter.confirm";
        private const string objKey = "obj";
        public SubscriberManager(IApiConfig config) : base(config)
        {
            fieldMng = new FieldManager(ApiConfig);
        }
        private async Task<JSON> GetObjValue(string groupId)
        {
            StringBuilder objVal = new StringBuilder("{");
            IEnumerable<FormWithQuests> formsWithQuests = await 
             fieldMng.GetFormsWithQuestsAsync();
            foreach (FormWithQuests formWithQuests in formsWithQuests)
            {
                IEnumerable<Quest> quests = formWithQuests.Quests;
                if (!quests.Any()) continue;

objVal.Append('"').Append(formWithQuests.Id).Append('"').Append(":{");
                for (int i = 0; i < quests.Count(); i++)
                {
                    Quest quest = quests.ElementAt(i);
                    objVal.Append('"').Append(quest.Id).Append('"')
                          .Append(':')
                          .Append('"').Append(quest.Name).Append('"');
                    if (i < quests.Count() - 1)
                    {
                        objVal.Append(',');
                    }
                }
                objVal.Append("},");
            }
            objVal.Append("\"-group\": {\"" + groupId + "\":\"1\"}}");
            return new JSON(objVal.ToString());
        }

        public async Task<int> ValidateRequestAsync()
        {
            try
            {
                IRestResponse resp = await GetResponseAsync(GetBaseRequest(getGroupsAPI)); // ! уже есть try-catch внутри
                return 200;
            }
            catch (Exception e)
            {
                Logger?.Error(e);
                return (int)HttpStatusCode.ServiceUnavailable;
            }
        }

            public async Task<Person> AddAsync(Subscriber subscriber)
            {
                Parameter request = GetBaseRequest(getMembersAPI).Parameters.FirstOrDefault(x => x.Name == "request"); // ! requestP
                request.Value = AddObjectToParam(request, emailKey, subscriber.Email);
                request.Value = AddObjectToParam(request, newbieConfirmKey, subscriber.OptIn ? "1" : "0");
                if (subscriber.OptIn)
                    request.Value = AddObjectToParam(request, newbieLetterConfirmKey, subscriber.TemplateId.ToString());
                request.Value = AddObjectToParam(request, objKey, (await GetObjValue(subscriber.GroupId)).Value);
                IRestResponse response = await GetResponseAsync(GetBaseRequest(getMembersAPI));
                SubscriberResponse data = GetDeserialized<SubscriberResponse>(
response.Content);
            return data.Member;
        }
    }

public class SendSayManager : ISendSayManager
{
    public IApiConfig ApiConfig { get; set; }
    public IListManager Lists { get; set; }
    public ISubscriberManager Subscribers { get; set; }
    public IFieldManager Fields { get; set; }
    public IEmailTemplateManager EmailTemplates { get; set; }
    public SendSayManager(IApiConfig apiConfig)
    {
        ApiConfig = apiConfig;
        Lists = new ListManager(ApiConfig);
        Subscribers = new SubscriberManager(ApiConfig);
        Fields = new FieldManager(apiConfig);
        EmailTemplates = new EmailTemplateManager(apiConfig);
    }
}

public class ListManager : BaseManager, IListManager
{
    public ListManager(IApiConfig config) : base(config)
    {
    }
    public async Task<IList<Group>> GetAllAsync()
    {
        IRestResponse response = await GetResponseAsync(GetBaseRequest(getGroupsAPI));
        ListResult lists = GetDeserialized<ListResult>(response.Content);
        return lists.List;
    }
}

public class FieldManager : BaseManager, IFieldManager
{
    private static class API
    {
        public static string GetForms { get { return "anketa.list"; } } // ! const бессмысленно? Раз отсутствует set
        public static string GetForm = "anketa.get"; 
    }
    private const string getFormsAPI = "anketa.list";
    private const string getFormAPI = "anketa.get";
    private const string requestName = "request";
    private const string idKey = "id";
    public FieldManager(IApiConfig config) : base(config)
    {
    }
    private async Task<string[]> GetFormIdsAsync(string apiMethod)
    {
        IRestResponse formsResp = await GetResponseAsync(GetBaseRequest(apiMethod));
        string[] formIds = GetDeserialized<FormList>(formsResp.Content).List.Select(x => x.Id).ToArray();
        return formIds;
    }
    private async Task<IEnumerable<QuestObj>> GetQuestsFromJSON(string formId)
    {
        IRestRequest readFormRequest = GetBaseRequest(getFormAPI);
        Parameter request = readFormRequest.Parameters.FirstOrDefault(x => x.Name == requestName);
        request.Value = AddObjectToParam(request, idKey, formId);
        IRestResponse readFormResp = await GetResponseAsync(readFormRequest);
        IEnumerable<QuestObj> questsFromJSON = GetDeserialized<RootObject>(readFormResp.Content).Obj.Quests.Select(x => x.Value);
        return questsFromJSON;
    }
    public async Task<IList<Quest>> GetAllAsync()
    {
        IList<Quest> quests = new List<Quest>();
        foreach (string formId in await GetFormIdsAsync(API.GetForms))
        {
            foreach (QuestObj quest in await GetQuestsFromJSON(formId))
            {
                quests.Add(new Quest(quest.Id, quest.Name));
            }
        }
        return quests;
    }
    public async Task<IEnumerable<FormWithQuests>> GetFormsWithQuestsAsync()
    {
        IList<FormWithQuests> formsWithQuests = new List<FormWithQuests>();
        foreach (string formId in await GetFormIdsAsync(API.GetForms))
        {
            IList<Quest> quests = new List<Quest>();
            foreach (QuestObj quest in await GetQuestsFromJSON(formId))
            {
                quests.Add(new Quest(quest.Id, quest.Name));
            }
            formsWithQuests.Add(new FormWithQuests(formId, quests));
        }
        return formsWithQuests;
    }
}

class JSON
{
    public string Value { get; set; }
    public JSON(string value)
    {
        Value = value;
    }
}

public class RootObject
{
    [JsonProperty("obj")]
    public Obj Obj { get; set; }
}
public class Obj
{
    [JsonProperty("quests")]
    public Dictionary<string, QuestObj> Quests { get; set; }
}
public class QuestObj
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("id")]
    public string Id { get; set; }
}


    class UserName
    {
        private const char delimeter = '|';
        public string Login { get; set; }
        public string Sublogin { get; set; }
        public UserName(string username)
        {
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentException("Username is not filled", nameof(username));
            }
            string[] logins = username.Split(delimeter);
            if (logins.Length == 2)
            {
                Login = logins[0];
                Sublogin = logins[1];
            }
            else
            {
                throw new ArgumentException($"The parameter {nameof(username)} has incorrect content.");
            }
        }
    }
Answer 1

Надо понимать, что в компании могут быть какие-то свои стандарты на код. Вам менеджер что-то по этому поводу давал почитать? Публичный кодревью на митингах проходил?

Сугубо мое личное.

1) Параметры в публичных методах нужно проверять перед их использованием. У вас ни в конструкторах классов, ни в публичных методах такой проверки не делается.

Было:

class UserName
{
    private const char delimeter = '|';
    public string Login { get; set; }
    public string Sublogin { get; set; }
    public UserName(string username)
    {
        Login = username.Split(delimeter)[0];
        Sublogin = username.Split(delimeter)[1];
    }
}

Стало:

class UserName
{
    private const char delimeter = '|';
    public string Login { get; set; }
    public string Sublogin { get; set; }
    public UserName(string username)
    {
        if (string.IsNullOrEmpty(username))
        {
            throw new ArgumentException($"The parameter {nameof(username)} is empty.");
        }
        //прежде чем присваивать данные из параметра нужно убедиться
        //в том, что они имеют условно правильные значения
        var logins = username.Split(delimeter);
        if (logins.Length == 2)
        {
            Login = logins[0];
            Sublogin = logins[1];
        }
        else
        {
            throw new ArgumentException($"The parameter {nameof(username)} has incorrect content.");
        }
    }
}

Кстати, в студии уже есть готовый функционал

2) Еще это.

Было:

public async Task<IList<List>> GetAllAsync()
    {
        try
        {
            var client = GetApiClient();
            var request = GetBaseRequest("group.list");
            var cancelTokenSrc = new CancellationTokenSource();
            var resp = await client.ExecuteTaskAsync<ListResult>(request, cancelTokenSrc.Token);
            if (resp.StatusCode != HttpStatusCode.Created && resp.StatusCode != HttpStatusCode.OK)
            {
                Logger?.Error($"Server return error: {resp.StatusCode}");
            }
            try
            {
                ListResult lists = JsonConvert.DeserializeObject<ListResult>(resp.Content);
                return lists.List;
            }
            catch (Exception e)
            {
                Logger?.Error(e);
                return null;
            }
        }
        catch (Exception e)
        {
            Logger?.Error(e);
            return null;
        }
    }

Стало:

//давать кастомные названия совпадающие с названиями классов .Net фреймворка
    //это как бы моветон, это я про ваш List
    public async Task<IList<List>> GetAllAsync()
    {
        //в try/catch должно быть только то, что потенциально может вызвать исключение
        var client = GetApiClient();
        //"group.list" - это значение должно быть конст.полем класса
        var request = GetBaseRequest("group.list");
        var cancelTokenSrc = new CancellationTokenSource();
        var resp = null; //укажите какой здесь должен быть тип
        try
        {
            resp = await client.ExecuteTaskAsync<ListResult>(request, cancelTokenSrc.Token);
            if (resp.StatusCode != HttpStatusCode.Created && resp.StatusCode != HttpStatusCode.OK)
            {
                Logger?.Error($"Server return error: {resp.StatusCode}");
            }
        }
        catch (Exception e)
        {
            //по-идее у вас должны быть свои кастомные классы исключений
            //получив здесь исключение, вы должны перебросить наверх уже
            //свое кастомное, кот. будет уже там наверху записано в лог
            Logger?.Error(e);
            return null;
        }
        // не надо делать матрешки их try/catch
        try
        {
            ListResult lists = JsonConvert.DeserializeObject<ListResult>(resp.Content);
            return lists.List;
        }
        catch (Exception e)
        {
            Logger?.Error(e);
            return null;
        }
    }
Answer 2

Критических ошибок я вижу две.

Первая - обработка ошибок. Никогда нельзя просто так брать и проглатывать исключение - это затрудняет диагностику и приводит к ситуации, когда программа просто не работает и ничего не понятно. У вас же по коду просто раскиданы пустые catch-блоки! Так делать нельзя. Добавьте хотя бы вывод в лог или какой-нибудь MessageBox. Можно использовать Trace.TraceError(...) если настройка логов не входит в задачу.

Вторая - вы зачем-то многократно парсите json. Парсите, потом преобразуете в строку и снова парсите... Нахрена?! Потратьте немного времени чтобы изучить что пакет newtonsoft.json умеет. К примеру, у всех токенов есть метод ToObject<...>. И забудьте про dynamic и ToString()! Это не те возможности которые следует использовать регулярно.

Исправление этих двух проблем переведет ваш код из категории "говнокод" в категорию "код новичка".

Answer 3

Мне не нравится, что вы возвращаете null вместо пустой коллекции, когда ловите ошибку.

Обычно, принято возвращать пустую коллекцию, что избавляет от того, что перед итерированием ее нужно проверять.

Answer 4

Упростил десериализацию:

 public class Quest
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
 var json2 = @"[{
                            'obj':{ 'order':['q957','q479','q214'],
                                    'quests':{
                                             'q214':{'width':10,'name':'Телефон','id':'q214','type':'free'},
                                             'q479':{'width':15,'name':'Имя','type':'free','id':'q479'},
                                             'q957':{'width':100,'name':'Город','id':'q957','type':'free'}
                                            },
                                    'id':'a525',
                                    'param':{'system':0,'name':'Данные пользователя', 'multi':0}},
                            'request.id':'fake -EC06814E-E35B-11E8-B22D-F854389B935E',
                            'duration':0.042354,
                            '_ehid':'212432.23074209719.1541684457'
                        }]";
dynamic root = JArray.Parse(json2);
IEnumerable<Quest> quests2 = ((IEnumerable<dynamic>)root[0].obj.quests).Select(x => (Quest)JsonConvert.DeserializeObject<Quest>(x.Value.ToString()));

Как ещё можно упростить? Нужно как-то избавиться от dynamic

READ ALSO
Подключится к MDB(MS Access) из PHP

Подключится к MDB(MS Access) из PHP

Как открыть файл базы MDB и выполнить запрос из под PHP ??? Люди добрые подскажите какие пакеты установить и как настроить на Debian Поиск информации...

153
Не передается значение value из select при отправке формы

Не передается значение value из select при отправке формы

При нажатии на кнопку submit файл indexphp обработка происходит у form

139
Проводник в браузере

Проводник в браузере

Всем приветИмею небольшой проводник который выводит в браузер список папок и файлов

180