0
\$\begingroup\$

I'm new to C# programming and has been following a certain intermediate tutorial that has asked me to write a stopwatch simulation code that includes an exception system:

Exercise 1: Design a Stopwatch

Design a class called Stopwatch. The job of this class is to simulate a stopwatch. It should provide two methods: Start and Stop. We call the start method first, and the stop method next. Then we ask the stopwatch about the duration between start and stop. Duration should be a value in Timespan. Display the duration on the console.

We should also be able to use a stopwatch multiple times. So we may start and stop it and then start and stop it again. Make sure the duration value each time is calculated properly.

We should not be able to start a stopwatch twice in a row (because that may overwrite the initial start time). So the class should throw an InvalidOperationException is it's started twice.

Educational tip: The aim of this exercise is to make you understand that a class should be always in a valid state. We use encapsulation and information hiding to achieve that. The class should not reveal its implementation detail. It only reveals a little bit, like a black box. From the outside, you should not be able to misuse a class because you shouldn't be able to see the implementation detail.

Transcribed from:

enter image description here


I have implemented code that does this exactly, however, I want to learn how to optimize my code to use fewer lines and make it more readable.

using System;

namespace Stopwatch_Training
{
    class Program
    {
        public class Stopwatch
        {
            private DateTime _startTime;
            private DateTime _stopTime;
            private TimeSpan _duration;
            public DateTime StartTime()

            {
                _startTime = DateTime.Now;
                return _startTime;
            }

            public DateTime Stoptime()

            {
                _stopTime = DateTime.Now;
                return _stopTime;
            }
        }

        static void Main(string[] args)

        {
            var stopwatch = new Stopwatch();
            int input1;
            DateTime starTime;
            DateTime endTime;
            double duration;
            int counter = 0;

            while (true)

            {
                Console.Write("1-Start Timer\n2-Stop Timer\n");
                input1 = Convert.ToInt32(Console.ReadLine());
                if (input1 == 1)
                {
                    starTime = stopwatch.StartTime();
                    counter = 1;
                }
                else
                {
                    throw (new InvalidOperationException("Please Start the timer with 1"));
                }

                Console.WriteLine("Please enter '2' to stop the timer whenever you want");
                input1 = Convert.ToInt32(Console.ReadLine());
                if (input1 == 2)
                {
                    endTime = stopwatch.Stoptime();
                    counter = 0;

                }

                else
                {
                    throw (new InvalidOperationException("Please end the timer with 2"));
                }
                if (counter == 1)
                {
                    throw (new InvalidOperationException("stuff"));
                }
                duration = (endTime - starTime).TotalSeconds;
                if (duration > 60.0)
                {
                    duration = (endTime - starTime).TotalMinutes;
                    Console.WriteLine(duration + "m");
                }
                else
                {

                    Console.WriteLine(duration + "s");
                }

            }
        }
    }
}
\$\endgroup\$
5
  • 3
    \$\begingroup\$ I have no idea how to implement an exception system in this case. - Unfortuantelly we cannot help you with that. Code Review is about improving working code, not about implementing missing features. You should read about how exeptions work first. \$\endgroup\$
    – t3chb0t
    Commented Nov 9, 2017 at 10:38
  • \$\begingroup\$ Oh I'm sorry, but I found a similar post of the same issue at stack overflow, and they advised him to post it here (didn't find his post though). \$\endgroup\$
    – Ibrahim
    Commented Nov 9, 2017 at 11:08
  • \$\begingroup\$ You may want to look at the existing System.Diagnostics.Stopwatch class for inspiration. Specifically its Elapsed and IsRunning properties, and the fact that none of its methods write anything to the console: that's not their responsibility, and it would add an unnecessary dependency which makes the class more difficult to reuse. \$\endgroup\$ Commented Nov 9, 2017 at 12:49
  • \$\begingroup\$ I have edited the post and added a new code that solves my question, however, I need help in optimizing my code to use fewer lines of code and perhaps improving its readability. \$\endgroup\$
    – Ibrahim
    Commented Nov 10, 2017 at 7:55
  • 2
    \$\begingroup\$ You can further improve the quality of your question by fully transcribing the image of the exercise. \$\endgroup\$
    – Mast
    Commented Nov 10, 2017 at 9:51

2 Answers 2

1
\$\begingroup\$

You have the beginning of a stopwatch representation—you have its data. You still need to add its behavior.

Approach your design of the class as if the class should stand on its own. Input/output will be handled by something else, like the console, but the stopwatch should be the authority on its internal state.

The simplest stopwatch has a start/stop button and a display. We'll interpret "having a display" to mean "a way to get the stored elapsed time".

This means we'll need to provide three methods:

  • Start: Sets the stopwatch in a running state.
  • Stop: Sets the stopwatch in an idle state.
  • TimeElapsed: Reads out the currently elapsed time.

Additionally, Start and Stop have the requirement that they should throw InvalidOperationException when the stopwatch is not a compatible state. Our stopwatch will need a notion of running to do this.

Let's look how we can modify the stopwatch class to adopt this behavior. I've annotated some below:

    public class Stopwatch
    {
        private DateTime _startTime; // we need this
        private DateTime _stopTime; // ? either this or duration
        private TimeSpan _duration;
        public DateTime StartTime() // why do we return a value here?

        {
            // we still need to check whether we are already running
            _startTime = DateTime.Now;
            return _startTime;
        }

        public DateTime Stoptime() // why do we return a value here?

        {
            // we still need to check whether we are still running
            _stopTime = DateTime.Now;
            return _stopTime;
        }

        // We still need the notion of 'currently running'
    }

    // First attempt
    public class Stopwatch
    {
        private DateTime _startTime;
        private DateTime _stopTime;
        private Boolean _running;

        public void StartTime()
        {
            if ( _running )
            {
                throw new InvalidOperationException("Cannot start: already running");
            }
            _startTime = DateTime.Now;
            _running = true;
        }

        public void Stoptime()
        {
            if ( !_running )
            {
                throw new InvalidOperationException("Cannot stop: not running");
            }

            _stopTime = DateTime.Now;
            _running = false;
        }

        public TimeSpan ElapsedTime()
        {
            if ( _running )
            {
                return DateTime.Now - _startTime;
            }
            else
            {
                return _stopTime - _startTime;
            }
        }
    }

Note that we check if the stopwatch is currently running when asking for elapsed time: otherwise, we're going to get a very strange reading.

Now we can 'clean up' a little bit to follow conventions:

  • Class members are implicitly private, so we don't need to add the private modifier.
  • Members should start with a capital letter.

Which gives us:

    public class Stopwatch
    {
        DateTime StartTime;
        Boolean IsRunning;
        DateTime StopTime;

        public void Start()
        {
            if (IsRunning)
            {
                throw new InvalidOperationException("Cannot start: already running");
            }
            StartTime = DateTime.Now;
            IsRunning = true;
        }

        public void Stop()
        {
            if (!IsRunning)
            {
                throw new InvalidOperationException("Cannot stop: not running");
            }
            IsRunning = false;
            StopTime = DateTime.Now;
        }

        public TimeSpan ElaspedTime()
        {
            if (IsRunning)
            {
                return DateTime.Now - StartTime;
            }
            else
            {
                return StopTime - StartTime;
            }
        }
    }

And a quick Main to play around with it:

    static void Main(string[] args)
    {
        var stopwatch = new Stopwatch();
        int choice;

        do
        {
            Console.Write("1-Start Timer\n2-Stop Timer\n3-Read Timer\n0-Quit\n");
            choice = Convert.ToInt32(Console.ReadLine());
            switch (choice)
            {
                case 1: stopwatch.Start(); break;
                case 2: stopwatch.Stop(); break;
                case 3: Console.WriteLine(stopwatch.ElaspedTime()); break;
            }
        } while (choice != 0);
    }
\$\endgroup\$
2
  • \$\begingroup\$ You have really transcribe the text from the image? Respect ;-) \$\endgroup\$
    – t3chb0t
    Commented Nov 11, 2017 at 18:50
  • \$\begingroup\$ @t3chb0t transcribinG iS morE fuN thaN televisioN ... ;-) \$\endgroup\$
    – JvR
    Commented Nov 11, 2017 at 20:11
-1
\$\begingroup\$

Here's how I implemented the solution, part of optimization I made StopWatach Class into a different file. Also made the Duration to be Displayed when both "Start" and "Stop" operations are performed.

private static void Main(string[] args)
{
    Console.WriteLine("*** Welcome to my Stop Watch ***");

    var stopWatch = new StopWatch();
    string choice;
    do
    {
        Console.WriteLine("1. Start 2. Stop ");
        choice = Console.ReadLine().ToLower();

        switch (choice)
        {
            case "start":
                stopWatch.Start();
                break;
            case "end":
                stopWatch.Stop();
                break;
        }

        if (!stopWatch._isRunning &&
            stopWatch._hasStopped) // Duration only prints when both start and stop Opeations are Completed 
            Console.WriteLine("Duration is: " + stopWatch.Duration());
    } while (choice == "start" || choice == "end" || !string.IsNullOrWhiteSpace(choice));
} 

and StopWatch.cs ,

public class StopWatch
{
    public DateTime _startTime;
    private DateTime _endTime;
    public bool _isRunning;
    public bool _hasStopped;

    public void Start()
    { 
        if (_isRunning)
            throw new InvalidOperationException("Already Started", new Exception("Stop Watch is already running"));

        _startTime = DateTime.Now;
        _isRunning = true;
    }

    public void Stop()
    {
        if (!_isRunning)
            throw new InvalidOperationException("Start First", new Exception("Please Enter \"Start\" to start Stop Watch"));
        
        _endTime = DateTime.Now;
        _isRunning = false;
        _hasStopped = true;
    }

    public TimeSpan Duration()
    {
        var duration = new TimeSpan();
        duration = _endTime - _startTime;
        return duration;
    }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Welcome to Code Review! Could you please edit to explain the benefit(s) of moving the StopWatach Class into a different file? \$\endgroup\$ Commented Jun 30, 2023 at 20:40
  • 1
    \$\begingroup\$ This is not a review. It does nothing to help the original poster from OVER 5.5 YEARS AGO. It is an alternative version. Unless you can explain to the OP WHY your code is better, this does not help. \$\endgroup\$
    – Rick Davin
    Commented Jun 30, 2023 at 20:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.