Класс Stopwatch


Я учусь C# и у меня есть упражнение:

Дизайн-класс под названием секундомер. Задача этого класса является моделирование секундомер. Он должен обеспечить двумя способами: запуск и остановка. Мы называем метод начать сначала, и метод остановки рядом. Затем мы просим секундомер о продолжительности между Start и Stop. Продолжительность должна значение в значение типа TimeSpan. Отображение длительности на консоли. Мы должны также мочь использовать секундомер несколько раз. Так что мы можем начать и остановить его и остановить его. Убедитесь, что значение длительности каждый раз рассчитано правильно. Мы не должны быть в состоянии начать секундомер два раза подряд (потому что может заменить первоначальный пуск времени). Поэтому класс должен бросить исключение InvalidOperationException, если его начали в два раза.

Мой код работает нормально, но, пожалуйста, комментарий, если я мог бы сделать что-то лучше. Особенно мне не нравится эта часть с выключателем блок но я не имею ни малейшего понятия, как я мог улучшить его.

Класс Stopwatch:

using System;

namespace Stopwatch
{
    public class Stopwatch
    {
        private DateTime _startDate;
        private DateTime _endDate;
        private bool _isRunning;

        public void Start()
        {
            if (_isRunning)
                throw new InvalidOperationException("Stopwatch is already running");

            _startDate = DateTime.Now;
            _isRunning = true;
        }

        public void Stop()
        {
            if (!_isRunning)
                throw new InvalidOperationException("Stopwatch is not running");

            _endDate = DateTime.Now;
            _isRunning = false;
        }

        public TimeSpan GetDuration()
        {
            return _endDate - _startDate;
        }

    }
}

Программа класса:

using System;

namespace Stopwatch
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopWatch = new Stopwatch();

            while (true)
            {
                Console.WriteLine("Enter 'start' to start Stopwatch\nEnter 'stop' to end Stopwach\nEnter any key to exit:\n");
                var input = Console.ReadLine().ToLower();

                if (input == "start" || input == "stop")
                    UseStopwatch(stopWatch, input);
                else
                    return;
            }
        }

        static void UseStopwatch(Stopwatch stopWatch, string command)
        {
            switch (command)
            {
                case "start":
                    try
                    { stopWatch.Start(); }
                    catch (InvalidOperationException)
                    { Console.WriteLine("stopWatch is already started\n"); }
                    break;
                case "stop":
                    try
                    {
                        stopWatch.Stop();
                        Console.WriteLine("Duration: {0}\n", stopWatch.GetDuration());
                    }
                    catch (InvalidOperationException)
                    { Console.WriteLine("stopWatch is not started\n"); }
                    break;
                default:
                    break;
            }
        }
    }
}


Комментарии
5 ответов

Во-первых, обязательные рекомендации, которые вы используете System.Diagnostics.StopWatch для этой цели, и не DateTime.Now (или даже UtcNow, который не ошибетесь, если вы случайно ввести летнее время во время выполнения программы). Используя Diagnostics.Stopwatch является более точным, чем методы, в DateTimeи это дает Elapsed свойство, которое возвращает TimeSpan.


GetDuration() это немного странно, потому что если вы называете это раньше Stop()тогда он вернет бред. Его надо или выкинуть, или, возможно, вычислить нынешнее время ellapsed, если запущен секундомер. В любом случае, это должны быть задокументированы (см. ниже).

Я бы также использовать собственность для GetDuration (как Heslacher предложил), если он не собирается бросать (т. е. когда Start() был вызван, но не Stop()), в этом случае, что может вывести из себя несколько перьев.


Как обычно, я рекомендую вам добавить некоторые встроенные-документация (///) в этих методах, которые должны объяснить, когда и почему исключения будут брошены (например, объяснить, что называя Start() два раза подряд не название, я бы быть уверены в том, что он бросает, не делает ничего, или перезапускает таймер).

/// <summary>
/// Starts the Stopwatch, resetting the elapsed time.
/// Throws an InvalidOperationException if the Stopwatch is already running.
/// </summary>
public static void Start()
{
// snip
}

Это не займет много времени, чтобы писать, и может улучшить интерфейс API массово.

10
ответ дан 15 марта 2018 в 12:03 Источник Поделиться

Хорошее усилие.

Несколько небольших пунктов, с большим количеством ссылок на мой блог на этом пути.

namespace Stopwatch
{
public class Stopwatch
{

Никогда не имя класса совпадает с его пространством имен. Мир боли ждет тех, кто. Мои четыре части серии статей, описывающих, что мир боли начинается здесь.

private DateTime _startDate;

Нет необходимости underbar частная полей в C#. Много людей делают. Это выглядит странно для меня.

public void Start()
{
if (_isRunning)
throw new InvalidOperationException("Stopwatch is already running");

Вам дается в изложении проблемы, Start не является идемпотентной. Выставить недвижимость на _isRunning так что звонящий может сказать, законно ли звонить Start прямо сейчас. Никогда не звонивший попробовать-catch, чтобы увидеть, если то, что они собираются сделать, является незаконным. Это досадные исключения и это очень плохо спроектирован.

    public void Stop()
{
if (!_isRunning)
throw new InvalidOperationException("Stopwatch is not running");

Вам не дано в условии задачи, что Stop это неидемпотентном. Это может быть разумный выбор Start и Stop имеют одинаковое поведение. Просто знайте, что это выбор вы сделали, не требует спец.

    public TimeSpan GetDuration()
{
return _endDate - _startDate;
}

Это может разумно быть геттер-только собственность.

Продолжительность не вычисляется правильно, если последовательность событий Startтогда GetDuration тогда Stop. Постановка задачи не сказано, что делать в этой ситуации. Что произойдет, если вы сделаете это? Это кажется разумным? Подумайте, что может быть более разумного поведения; что ваша интуиция о реальных секундомеры сказать вам, должно быть сделано здесь?

Аналогично: что произойдет, если GetDuration называется до Start ? Отработать все возможные сценарии для упорядочивания ваших трех точек входа и придумать разумный спецификация для каждого. Затем реализует эту спецификацию и написать несколько тестов.

              try
{ stopWatch.Start(); }
catch (InvalidOperationException)
{ Console.WriteLine("stopWatch is already started\n"); }

И теперь мы видим, почему досадные исключения так коряво. Если запуска и остановки не являются идемпотентными, то правильно здесь должны быть:

if (!stopWatch.Running)
stopWatch.Start();
else
Console.WriteLine("stopWatch is already started\n");

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

           default:
break;

Ненужные. Удалить его.

Дополнительные упражнения:


  • Теперь реализовать секундомер, который был операциями выполняется, продолжительность, старт, пауза, возобновление и остановка. Опять же, отработать всех возможных упорядочивания всех возможных операций и убедитесь, что каждая операция имеет разумное поведение. Добавив еще две операции, делает это значительно сложнее проблема. Рассмотреть вопрос об изучении конечных автоматов теория; это теория, которая лежит в основе такого рода объекта.

  • Остановить с помощью консоли. Сделать WPF или проект, Старт / Стоп кнопки и т. д. на контроль секундомер приложения WinForms. Отображения времени в управления.

10
ответ дан 16 марта 2018 в 12:03 Источник Поделиться

Вы сделали хорошо. Вы создали класс для секундомер, который только ответственность-это именно то, что секундомер должен сделать : пуск, остановка и рассчитать продолжительность.

То, что я не люблю такие сообщения, исключений не потому, что они могут быть неправы или вводящей в заблуждение, а потому, что вы показываете другое сообщение catch блок. Вы можете легко изменить это как так

case "start":
try
{ stopWatch.Start(); }
catch (InvalidOperationException ex)
{ Console.WriteLine(ex.Message); }
break;

но если вы хотите изменить его, как так

case "start":
try
{
stopWatch.Start();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
}
break;

было бы более читаемо с первого взгляда. Читабельность-это главное, если дело доходит до сохранения кода, означает исправление ошибок или добавление функций. Вы не хотите читать один и тот же код 3 раза, пока не поймете, о чем идет речь.

Если вы позволите static void UseStopwatch() вернуть bool которое вы установили на false в default: случае вы можете использовать возвращаемое значение, как при условие. Как так

bool shouldRun = true;
while (shouldRun)
{

shouldRun = UseStopwatch(stopWatch, input);
}

таким образом, вы могли бы опустить if..else из Main() .

Кстати. ловить наиболее конкретное исключение, как ты сделал, является способом пойти.


Что бы я изменил О Stopwatch что я хотел бы использовать свойство Duration вместо того GetDuration() метод.

9
ответ дан 15 марта 2018 в 12:03 Источник Поделиться

Вы действительно очень хорошо, однако есть одна большая проблема, что бы сделать ваш класс не может использоваться в производственной среде. Не забывайте DateTime.Now Это момент времени, который является изменяемым пользователем или другими механизмами вы не можете повлиять (например, перехода на летнее время). Так что вы можете легко вернуться к некорректным результатам, которые могут быть даже негативные - просто попробуйте переместить часы назад в то время как секундомер работает!

При измерении продолжительностью не использовать разность двух DateTimeС. Если вы не можете использовать Stopwatch по какой-либо причине, придерживаться чего-то, что не влияет календарная дата, например окружающей среды.TickCount. Создание TimeSpan объект с разницей в два TickCountС не должно быть проблемой для вас.

6
ответ дан 15 марта 2018 в 02:03 Источник Поделиться

Кроме того, что уже упоминалось в других ответов, я бы хотел указать на проблему не сброс _endDate когда вы запускаете таймер.

Все будет хорошо для первого запуска, но если вы повторно использовать объект timer, на второй старт _endDate будет по-прежнему установлен. Смысл любой вызов GetDuration буду стараться возвращать отрицательное значение TimeSpan, поскольку он будет использовать время из прошлого.

4
ответ дан 15 марта 2018 в 06:03 Источник Поделиться