-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Бессараб Дмитрий #31
base: master
Are you sure you want to change the base?
Бессараб Дмитрий #31
Conversation
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Видимо остались старые ненужные зависимости. Их лучше очищать, чтобы не засорять код
{ | ||
public interface ITextProcessor | ||
{ | ||
public Dictionary<Word, int> Words(string path, ITextProvider provider, params IWordFilter[] filters); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кажется фильтры - это всё-таки информация для конструктора, а не конкретного метода. Почему выбрал делать именно так?
Хотел предложить сделать обработку уже прочитанного текста, а не читать внутри этого же класса из файла. Получается очень жёсткая связь в поведении. У нас есть штука, которая умеет класса подготавливать слова, но получить их может только из файла
var words = new Dictionary<Word, int>(); | ||
foreach (var word in GetWordsFromString(provider.ReadFile(path))) | ||
{ | ||
var flag = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Очень абстрактное название для переменной. Давай придадим смысла
foreach (var filter in filters) | ||
{ | ||
if (flag == false) break; | ||
if (filter.Skips(word)) continue; | ||
flag = false; | ||
} | ||
if (flag == false) continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Как сложно получилось 😨
Есть предложение реализовать здесь паттерн фильтра (немного отличается от привычного значения) и упростить этот код. А так же дать возможность легко расширять набор этих предусловий
Вот здесьможно почитать про паттерн фильтра. Нашёл хорошую статью только для Java, но она очень похожа на C#. Если будут вопросы, разберём
flag = false; | ||
} | ||
if (flag == false) continue; | ||
if (!words.TryGetValue(word, out var _)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Здесь лучше подойдёт ContainsKey по значению
А ещё есть практика делать универсальный код с небольшими подстановками. Вот что имею в виду
// Подготовка данных, на случай если ещё нет ничего
if (!words.ContainsKey(word))
words[word] = 0;
// Универсальное поведение для всех слов вне зависимости от происходящего
words[word] += 1;
|
||
namespace TagsCloudContainer.WordFilters | ||
{ | ||
public class SimpleFilter : IWordFilter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Может тогда BoringWordFilter?
{ | ||
public class SimpleFilter : IWordFilter | ||
{ | ||
private readonly HashSet<string> _words = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Дадим более узкое название этому списку исключений? А то только по операциям над ним становится понятно, что это такое
TagsCloudContainer/Tag.cs
Outdated
Font = font; | ||
Color = color; | ||
var rect = g.MeasureString(Value, Font).ToSize(); | ||
Frame = new Size(rect.Width + 2, rect.Height + 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Подскажи, для чего нужно добавлять магическое число к размерам?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кстати, размеры можно считать "снаружи" и передавать уже готовый size. Тогда это станет record
TagsCloudContainer/Program.cs
Outdated
var provider = new TxtTextProvider(); | ||
var filter = new SimpleFilter(); | ||
var processor = new TextProcessor.TextProcessor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Комментарий на будущее. DI контейнер заменит это на красивый блок кода, где не нужно будет задумываться над тем, как кого создать
} | ||
|
||
[Test] | ||
public void ThrowException() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не хватает в названии условия, при котором бросать исключение
public bool RandomColor; | ||
public Color Color { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Названия у этих свойств не очень отражают суть. Получится придать им смысла? И ещё не очень понятно, как быть в ситуации, когда RandomColor=true
и цвет тоже указан. Можно посмотреть в сторону nullable типов
var container = new ContainerBuilder(); | ||
|
||
container.RegisterType(config.PointGenerator) | ||
.AsImplementedInterfaces() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Писать один раз удобно, но в поддержке тяжело. Поиском не найти, где этот интерфейс применяется и с кем связан. А ещё возможны потенциальные конфликты, так как иногда один класс реализует несколько интерфейсов, и не ясно, к какому его использовать. А ещё вложенное наследование
.AsImplementedInterfaces() | ||
.SingleInstance(); | ||
else | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Фигурные скобки сильно путают. Если у if
их нет, то скорее всего в else
тоже они не нужны
|
||
namespace TagsCloudContainer.PointGenerators | ||
{ | ||
[Label("Спираль")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это интересная техника, хорошо что прочитал. Но здесь на мой взгляд это перебор. Если что-то подобное делать, то нужно использовать локализации, а это уже совсем другая история. Предлагаю остановиться просто на названиях классов или даже цифрах при выборе. Это уже будет определяться реализацией клиента (ui или console)
|
||
namespace TagsCloudContainer.TagGenerator | ||
{ | ||
public class RandomColorTagGenerator(ITextProcessor processor, Graphics graphics, Font defaultFont) : ITagsGenerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Конструктор по умолчанию class Foo(prop1, prop2, prop3)
может быть только у классов без методов (например record). Дело в том, что все эти поля создаются не readonly, что повышает риск ошибки
TagsCloudContainer/Program.cs
Outdated
{ | ||
internal class Program | ||
{ | ||
static void Main(string[] args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если код ниже - эксперименты - хорошо. Но в итоговом решении его стоит вынести в отдельную реализацию клиента, и в Main вызывать уже его запуск
TagsCloudContainer/Program.cs
Outdated
|
||
} | ||
|
||
private static void ConfigureColor(Config config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А вот вручную вызывать Assembly не нужно. Это ровно противоположное действие использованию DI контейнера. Задать возможные цвета через консоль поможет одна из библиотек парсинга аргументов. Например такой. Это заметно упростит обработку и поддержку кода. Чем более строгая типизация - тем лучше. Думаю, можно прямо на этапе разработки добавить нужные значения в аргументы. Они редко будут меняться. А что самое главное, при их изменении IDE подскажет, где есть использование, что позволит явно проконтролировать обратную совместимость (новая версия не ломает старое использование)
К тому же всё это теряет смысл в момент парсинга цвета, который производится вручную через switch
TagsCloudContainer/Program.cs
Outdated
var pointGenerators = FindImplemetations<IPointGenerator>(); | ||
foreach (var point in pointGenerators) | ||
Console.WriteLine(point.Key); | ||
Console.WriteLine("Введите, соблюдая орфографию"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аналогично предыдущему. Если хочется дать такую гибкую возможность конфигурирования, то можно воспользоваться функцией di возвращать набор зарегистрированных реализаций (container.Resolve<IEnumerable<IPointGenerator>>())
. Тогда пользователь сможет выбрать только из того, что заведомо работает и поддерживается.
А ещё писать целиком - это опасное дело. Приятнее для человека указать одну цифру, чем вручную писать целом слово
TagsCloudContainer/Tag.cs
Outdated
Font = font; | ||
Color = color; | ||
var rect = g.MeasureString(Value, Font).ToSize(); | ||
Frame = new Size(rect.Width + 2, rect.Height + 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Кстати, размеры можно считать "снаружи" и передавать уже готовый size. Тогда это станет record
TagsCloudContainer/Tag.cs
Outdated
|
||
public Tag(Graphics g, Word word, Font font, Color color) | ||
{ | ||
Value = word.Value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Точно нужно доставать значение из Word? Можно его сохранить как самодостаточную единицу, вдруг что-то понадобится в него добавить
using var scope = container.BeginLifetimeScope(); | ||
scope.Resolve<PictureMaker>().DrawPicture(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно использовать без усложнений со скоупами, так как жизненный цикл всех сущностей совпадает с временем жизни всего приложения. Просто у контейнера вызывать Resolve
Client/Program.cs
Outdated
using System.Drawing; | ||
using System.Reflection; | ||
using System.Threading.Channels; | ||
using TagsCloudContainer.Configuration; | ||
using TagsCloudContainer.PointGenerators; | ||
using TagsCloudContainer.StringParsers; | ||
using TagsCloudContainer.TextProviders; | ||
using TagsCloudContainer.WordFilters; | ||
using System.Linq; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.ComponentModel; | ||
using TagsCloudContainer; | ||
using TagsCloudContainer.TextProcessor; | ||
using Autofac; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Очень много лишних using (
Client/Program.cs
Outdated
var xLine = Console.ReadLine(); | ||
var yLine = Console.ReadLine(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Без кода никогда не догадаться, в каком формате вводить координаты. Пользователю нужно давать как можно больше подсказок. Желательно все ReadLine заменить на что-нибудь такое
var xLine = ReadValue("Координата Х");
var yLine = ReadValue("Координата Y");
private static string? ReadValue(string? argName = null)
{
Console.Write($"{argName ?? ""}: ");
return Console.ReadLine();
}
Client/Program.cs
Outdated
if (colors.ContainsKey(inp)) | ||
{ | ||
config.Color = Color.FromName(colors[inp].ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ContainsKey под капотом уже получает значение по ключу, а потом снова происходит поиск по этому же ключу. Здесь это чревато только повторением одного и того же действия. В многопоточном коде это может привести даже к ошибке
поток 1: colors.ContainsKey(inp)
=> true
поток 2: colors.Remove(inp)
поток 1: colors[inp]
=> KeyNotFoundException
И такие ситуации очень неожиданные, ведь только что проверяли, что ключ есть. Поэтому если требуется в итоге работать со значением, то лучше использовать TryGetValue. Если же нужно лишь убедиться в наличии, то ContainsKey по семантике подходит лучше
Client/Program.cs
Outdated
Console.WriteLine("\t" + point.Key); | ||
Console.WriteLine("Введите, соблюдая орфографию"); | ||
var pointGenerator = Console.ReadLine().ToLower(); | ||
if (pointGenerators.ContainsKey(pointGenerator)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Аналогично цвету, лучше использовать TryGetValue, но не критично здесь
Client/Program.cs
Outdated
var inp = Console.ReadLine().ToLower(); | ||
if (colors.ContainsKey(inp)) | ||
{ | ||
config.Color = Color.FromName(colors[inp].ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Color.FromName - почти наверняка сработает, так как поддерживает много цветов. Но безопаснее было бы сделать в библиотеке в качестве параметра конструктора у класса GolorProvider значение enum, а потом его уже в switch преобразовывать в цвет. Таким образом, если вместо color придётся использовать какой-нибудь UnixColor, для внешних пользователей ничего не поменяется в использовании
[TestFixture] | ||
public class CloudLayoutShould | ||
{ | ||
private CloudLayout layout; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно не выносить в общее поле, ведь нигде в других местах его не нужно переиспользовать. А лишний раз делать общие переменные плохо сказывается на параллельности выполнения
public void SetRightFontSize() | ||
{ | ||
var bitmap = new Bitmap(1, 1); | ||
using var graphics = Graphics.FromImage(bitmap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Графика нигде не используется
|
||
namespace TagsCloudContainer.Tests | ||
{ | ||
public class RandomColorTagGeneratorShould |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это ведь тест на два независимых объекта: TagGenerator и RandomColorProvider. Сложно будет понять, из-за кого пошло не так, при последующей разработке
public void Process() | ||
{ | ||
var result = new TextProcessor.TextProcessor( | ||
new TxtTextProvider(@"TextFile1.txt"), new RegexParser(), new BoringWordFilter(), new ShortWordFilter()).WordFrequencies(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Надежнее сделать какой-нибудь тестовый TextProvider, который будет просто текст из строки возвращать. Это избавит от возможных ошибок из-за работы TxtTextProvider и слова будут на виду в тесте
|
||
public class ColorProvider : IColorProvider | ||
{ | ||
[CompilerGenerated] private readonly Color _color; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Думаю, здесь не нужен системный атрибут [CompilerGenerated]
, ведь этот код пишешь ты сам
@glazz200