Made refactoring. What work was carried out:

  1. divided into methods, rendered many in BaseManager

  2. simplified variable names

  3. transferred all try-catch to the base class BaseManager

  4. grouped methods by access (at the top private, hereinafter public)

  5. It was:

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

    It became:

     protected RestClient ApiClient { get { return new RestClient { BaseUrl = new Uri(_baseApiServer) }; } } 
  6. It was

     private async Task<string> GetObjVal(string groupId) 

    It became:

     private async Task<JSON> GetObjValue(string groupId) 

    now you can see that the method returns JSON. For this, I had to enter the JSON class, and in it the Value field

  7. Made constants:

     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"; } 

    motive - to get rid of the API prefix in constants

  8. Which is better

     Parameter request = .... 

    or better

     Parameter requestParam 

    or

     requestP 

    make it clear that this is a parameter. Or you can understand the type by pointing the mouse?

  9. In UserName.cs, I throw an exception, as advised. But everywhere the error code is written to the log. According to the style of the previous code, do I also need to write to the log?

  10. JSON.cs, RootObject.cs, UserName.cs placed in the Model folder

  11. Get rid of dynamic

If any points are incorrect, please correct. + write what else to fix

 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."); } } } 
  • complain about the quality - that is, they have quality criteria, about the need to match the code with which you were notified before you started writing all this; or how? What quality problems have been identified in this code? - VTT Nov.
  • the main complaint is incorrect deserialization, where the delegate is. used dynamic, return (dynamic) - olegall
  • I did not have time to refactor - and they need my perfect code to be perfect and immediately ready for production - olegall
  • one
    And you wrote unit tests? - Bulson
  • one
    what comes in readFormResp.Content , why frame it in [] and parse as an array? and then take only the first element ??? - Grundy

4 answers 4

It should be understood that the company may have some kind of code standards. Did the manager give you something to read about this? Did public coding on rallies take place?

Especially my personal.

1) Parameters in public methods need to be checked before using them. You do not do such a check either in class constructors or in public methods.

It was:

 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]; } } 

It became:

 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."); } } } 

By the way, the studio already has ready functionality.

call the context menu on the method parameter call the refactoring menu

2) Another is.

It was:

 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; } } 

It became:

 //давать кастомные названия совпадающие с названиями классов .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; } } 

    I see two critical errors.

    The first is error handling. You can never just take and swallow an exception - this makes it difficult to diagnose and leads to a situation where the program simply does not work and nothing is clear. You have the same code just scattered empty catch-blocks! So you can not do. Add at least an output to the log or some MessageBox. You can use Trace.TraceError(...) if logging is not included in the task.

    The second is why you parse the json multiple times. Parsite, then convert to string and parse again ... Fuck ?! Take some time to learn what the newtonsoft.json package can do. For example, all tokens have a ToObject<...> method. And forget about dynamic and ToString ()! These are not opportunities that should be used regularly.

    Fixing these two problems will translate your code from the "govnokod" category to the "novice code" category.

      I don't like the fact that you return null instead of an empty collection when you catch an error.

      Usually, it is customary to return an empty collection, which eliminates the fact that it needs to be checked before iteration.

      • it was before me and I kept the style - olegall

      Simplified deserialization:

        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())); 

      How else can you simplify? Need to somehow get rid of dynamic

      • one
        the speaker is not needed, since JArray.Parse , oddly enough, returns an object of type JArray - Grundy
      • one
        Use the "edit" button to add to the question. Answers for this purpose are not intended. - Pavel Mayorov