Requesting a code review part two

This is the second time I’ve asked you to review my code. This time I used codepal.ai and the famous linters for Python to make it easier for you to review my code. I wanted to be fair in the first review, since I didn’t try to improve it. However, I did learn something from the first review, such as the magic number and the importance of error handling. With your insight and ChatGPT, I’m confident I can learn more efficiently and improve my code. Any criticism or insight is welcome.

import argparse
import logging
import secrets
import subprocess
import sys

# Set up logging
logging.basicConfig(
    level=logging.INFO, format="\n%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
logger.info("Logging initialized")

# Constants
YES = "yes"
EXIT = "exit"
CHANGE = "change"
MAX_NUMBER_LENGTH = 2

# Run the command
COMMAND = "/bin/echo"
ARGS = "Hello, World!"

# Check the output
EXPECTED_OUTPUT = "Hello, World!"

# Define constant tuples
PREDEFINED_STRINGS = (
    "Why did Python developers always write unit tests first? "
    "Because they don't want to get bit by a bug!",
    "Why did the Python script get a job in testing? "
    "Because it was an expert in handling exceptions!",
    "Why did the Python script cross the road? "
    "To get to the other side of the integration test!",
    "Why did the Python script fail the integration test? "
    "Because it couldn't handle the stress of working with other modules!",
    "Why did the Python script get lost in the testing environment? "
    "Because it forgot to import the os module!",
    "Why did the Python script fail the performance test? "
    "Because it was too slow, like a python in hibernation!",
)

FRUITS_LIST_FILE = "./fruits.list"


class CalledProcessError(Exception):
    """
    Exception raised when a command fails.
    """
    def __init__(self, message):
        """
        Initializes the exception.

        Args:
            message (str): The error message.
        """
        self.message = message


def run_command(command: str, *args: str) -> str:
    """
    Executes a command with arguments securely.

    Args:
        command (str): The command to execute.
        *args (str): The arguments to pass to the command.

    Returns:
        str: The output of the command.

    Raises:
        ValueError: If the command or any of the arguments are not strings.
        CalledProcessError: If the command fails.
        OSError: If an OS-level error occurs while running the command.
    """
    logger.info("Running command %s with string %s", command, args)

    try:
        if not isinstance(command, str):
            raise ValueError("Command must be a string")
        if not all(isinstance(arg, str) for arg in args):
            raise ValueError("All arguments must be strings")
        # Sanitize input
        command = command.strip()
        args = tuple(arg.strip() for arg in args)
        # Use subprocess securely
        result = subprocess.run(
            [command, *args],
            check=True,
            shell=False,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            stdin=subprocess.PIPE,
            timeout=5,
            text=True,
            encoding="utf-8",
        )
        # Log output
        logging.info(
            "Command '%s %s' executed successfully with output: %s",
            command,
            args,
            result.stdout,
        )
    except subprocess.CalledProcessError as error:
        logging.error(
            "Command '%s %s' failed with return code: %s\n%s",
            command,
            args,
            error.returncode,
            error.stderr,
        )
        raise CalledProcessError(
            f"Command '{command} {args}' failed with return code: "
            "{error.returncode}\n{error.stderr}"
        ) from error
    except OSError as error:
        logging.error(
            "An OS-level error occurred while running the command '%s %s': %s",
            command,
            args,
            error,
        )
        raise OSError(
            "An OS-level error occurred while running "
            f"the command '{command} {args}': {error}"
        ) from error
    return result.stdout


def prompt_user_for_action():
    """Prompts the user for an action.

    Returns:
    str: The user's input.
    """
    try:
        while True:
            answer = input("Options: Yes, Exit, Change\nProceed? ").lower()
            if answer == YES:
                break
            if answer == EXIT:
                logger.info("User exited the script")
                sys.exit(0)
            if answer == CHANGE:
                change_user_input()
            else:
                logger.error("The answer should be (yes|exit|change) only.")
    except ValueError:
        logger.error("That was not a valid input. Please try again.")
    except KeyboardInterrupt as err:
        raise KeyboardInterrupt(
            "User interrupted the script. Exiting..."
        ) from err


def change_user_input():
    """Prompts the user for a new number and updates the argument."""
    while True:
        try:
            user_input = input(
                f"Please enter a new number (max {MAX_NUMBER_LENGTH} digits): "
            )

            while len(user_input) > MAX_NUMBER_LENGTH:
                logger.error(
                    "Number must be at most %s digits long.", MAX_NUMBER_LENGTH
                )
                user_input = input("Enter an integer: ")

            # Convert the input to an integer
            params.u = int(user_input)
            logger.info("User input changed to %s", params.u)
            break
        except ValueError:
            logger.error("That was not a valid integer. Please try again.")
        except KeyboardInterrupt as error:
            raise KeyboardInterrupt(
                "User interrupted the script. Exiting..."
            ) from error


def get_random_fruit():
    """
    Reads the fruits list file and returns a random fruit from it.

    Logs an error if the file is not found.
    """
    try:
        with open(FRUITS_LIST_FILE, "r", encoding="utf-8") as fruits_list_file:
            lines = fruits_list_file.readlines()
            random_fruit = secrets.choice(lines)
            fruit_string = random_fruit.strip()
            return fruit_string
    except FileNotFoundError:
        logger.error("Fruits list file not found: %s", FRUITS_LIST_FILE)
        sys.exit(2)


def check_arguments(parsed_args):
    """Check if the given arguments are valid.

    Args:
        parsed_args (argparse.Namespace): The parsed arguments

    Raises:
        SystemExit: If the given arguments are not valid
    """
    try:
        if parsed_args.r and parsed_args.t:
            error_message = "Arguments -r and -t cannot be used together."
            logger.error(error_message)
            raise SystemExit(error_message)
        if parsed_args.A and parsed_args.R:
            error_message = "Arguments -R and -A cannot be used together."
            logger.error(error_message)
            raise SystemExit(error_message)
        logger.info("Arguments checked successfully")
    except Exception as error:
        logger.error("Error checking arguments: %s", error)
        raise SystemExit(f"Error checking arguments: {error}") from error


def parse_command_line_arguments():
    """Parse command-line arguments.

    Returns:
        argparse.Namespace: The parsed arguments
    """
    # Parse command-line arguments.
    parser = argparse.ArgumentParser(description="Process a few arguments.")
    parser.add_argument(
        "-u", type=int, help="Number to be entered", metavar=""
    )
    parser.add_argument("-r", action="store_true", help="...")
    parser.add_argument("-R", action="store_true", help="...")
    parser.add_argument("-s", action="store_true", help="...")
    parser.add_argument("-A", action="store_true", help="...")
    parser.add_argument("-l", action="store_true", help="...")
    parser.add_argument("-t", action="store_true", help="...")
    parser.add_argument("-d", action="store_true", help="...")
    args = parser.parse_args()
    return args


if __name__ == "__main__":
    try:
        params = parse_command_line_arguments()
        check_arguments(params)
        logger.info("Arguments parsed successfully")
    except SystemExit as exc:
        logger.error("Error parsing arguments: %s", exc)
        sys.exit(2)

    output = run_command(COMMAND, ARGS)
    if EXPECTED_OUTPUT not in output:
        logging.error(
            "The command '%s %s' did not return the expected output",
            COMMAND,
            ARGS,
        )
        sys.exit(1)
    else:
        logging.info(
            "Command executed successfully and returned expected output"
        )

    try:
        prompt_user_for_action()
    except KeyboardInterrupt:
        logger.info("User interrupted the script. Exiting...")
        sys.exit(0)

    if params.r:
        fruit = get_random_fruit()
        run_command("/bin/echo", f"{fruit}")

    if params.R:
        run_command("/bin/echo", PREDEFINED_STRINGS[0])

    if params.s:
        run_command("/bin/echo", PREDEFINED_STRINGS[1])

    if params.A:
        run_command("/bin/echo", PREDEFINED_STRINGS[2])

    if params.l:
        run_command("/bin/echo", PREDEFINED_STRINGS[3])

    if params.t:
        run_command("/bin/echo", PREDEFINED_STRINGS[4])

    if params.d:
        run_command("/bin/echo", PREDEFINED_STRINGS[5])

Start by dropping ChatGPT. It does not understand code and it will not give you good code.

ChatGPT is a language model, it is good at sounding like human speech. It does not know about facts (even “obvious” ones - you can fool it into arguing that 2+2 is 5), and it does a terrible job of writing software, because it was never taught to write software.

And what you’ve posted here is a HUGE pile of code, most of which is completely unnecessary (the exception could literally just be class CalledProcessError(Exception): pass and it would behave identically). It’s made an awful habit of soft coding with unnecessary constants at the top of the code, achieving nothing and foricng you to read out-of-line declarations instead of being able to read what the code’s doing right where it is. This code in particular is something I do not want to see in production:

    if params.r:
        fruit = get_random_fruit()
        run_command("/bin/echo", f"{fruit}")

    if params.R:
        run_command("/bin/echo", PREDEFINED_STRINGS[0])

So, my advice to you is: Ditch the AI and learn from actual programmers. You’ll do way WAY better.

3 Likes

Note that I’ve fixed undefined variables in check_arguments function.

Thanks for your criticism, I am aware that AI is just a tool and that is why I am asking for a review of my code.

Yes, but my point is that it’s a tool that’s worse than useless, and you will do better to learn from people not LLMs. I don’t want to go into detail about the code you’ve posted because there are just so many things about it that are suboptimal, and most of them aren’t your fault. If you were to write your own code and post it for review, without ANY involvement from ChatGPT, it would likely be considerably better code, and also, you would learn far more from the code review.

3 Likes

You are right, by the way. I only mentioned ChatGPT to ask in case I didn’t understand something about the review of my code, and codepal.ai is the AI I used to refactor my code, which isn’t perfect either.

Most of the time ChatGPT just unnecessarily bloats my code, sometimes it helps, but I’ll take your suggestion and ask the community more instead.

I’m sorry if the jokes are offensive, I didn’t think twice it might be inappropriate.

Other that the “don’t use AI” advice (note, I have no idea how good or bad AI is at writing code – I heard at least one anecdote that it was pretty good at optimizing already working code) – even if it wrote great code, you don’t learn well that way!

Major note: don’t use logging to provide output to your user. There are times when you want a logger to write to stdout, but those are different use cases – just use print()

If you want a bunch of canned strings for wuestions and answers, put them in some sort of data type – maybe even a tuple: (‘question’, ‘answer’)

I got bored look at it any more …

-CHB

@PythonCHB and @Rosuav

I have a few more questions, but I don’t have much time, so I’ll ask them next time.

Now I can see that there might be some kind of misunderstanding.

So I want to ask if you guys think that I just ask ChatGPT to generate a code once and just blindly copy and paste it, If not, how many days do I work with my code, or how many times have I refactored or edited my code, do you think?

If you start constructing a building on a seashore with nothing but sand underneath it, how many days do you have to spend sweeping the sand level before it’s safe to build on it?

You can’t start with a bad foundation and build something good on it. Start with a foundation built on actual expertise (from other programmers), and then build your own structure on top of it. Get to know how (and why) the foundation is the way it is.

1 Like

I really like the way you’ve explained it to me, so I’m going to surrender.

Please tell me if my solution is okay, I don’t have much time, so I will tell you my solution next time.

As long as you understand that it doesn’t understand code. I think of
LLMs as understand statistical assoications in very large bodies of
text, so that it knows that “code looking like this is often associated
by descriptive text like the text you asked for”. There’s more going on
than that as far as I understand, but the underlying essence is
conceptually shallow - it is regurgitating and combining patterns, not
solving problems.

Given that background, you need to examine the code yourself to see if
it really does what you asked for. And whether it is concise or bloated.

1 Like

Do you agree that I can use ChatGPT to finish my code, but ask the community to review it instead of asking the AI to do a refactoring of my code?

To be quite frank, no I don’t. I don’t think it offers any value. It is NOT something designed for writing code, and it will not produce better code than you could produce yourself with a bit of effort. (That’s not to say that AIs will never produce good code, but ChatGPT is not that AI.)

Just write your code and then ask intelligent people to review it. That way, every bit of feedback you get can actually help YOU to learn to write code, rather than simply being feedback about the kind of code the AI generated. How would you make any use of that in the future? What value is it if we review code that was generated by ChatGPT? It’s not like we’re helping to train the AI - not in any meaningful way.

I would be happy to review code that you write, but not code that came from something you have no control over.

Example of finished code, but ChatGPT finished my code without adding any other AI bloat to it, is my code v0.0.

import argparse
import sys
import subprocess
import secrets
#import pdb

#pdb.set_trace()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='...')

    # Add the arguments
    parser.add_argument('-u', type=int, help='...')
    parser.add_argument('-r', action='store_true', help='...')
    parser.add_argument('-R', action='store_true', help='...')
    parser.add_argument('-s', action='store_true', help='...')
    parser.add_argument('-A', action='store_true', help='...')
    parser.add_argument('-l', action='store_true', help='...')
    parser.add_argument('-t', action='store_true', help='...')
    parser.add_argument('-d', action='store_true', help='...')

    # Parse the arguments
    args = parser.parse_args()
    
    try:
        output_bytes = subprocess.check_output("/bin/echo Hello, World", shell=False)
        # Do something with the output
    except subprocess.CalledProcessError as e:
        # Handle the error
        print(f"Command failed with return code {e.returncode}: {e.output}", file=sys.stderr)
        sys.exit(1)
    except UnicodeDecodeError as e:
        # Handle the error
        print("Error decoding output from command:", e, file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        # Catch-all for any other exceptions
        print("Unexpected error:", e, file=sys.stderr)
        sys.exit(1)

    output = output_bytes.decode()
    if "Hello, World!" not in output:
        try: 
            subprocess.run(["/bin/echo", "Why did the Python script fail its unit test? Because it was afraid of snakes!"], check=True) 
            print("Command executed correctly") 
        except subprocess.CalledProcessError as e: 
            print("Command failed with return code:", e.returncode, file=sys.stderr)
            sys.exit(1)

    while True:
        try:
            answer = input("Proceed? ")
        except KeyboardInterrupt: 
            # Handle the KeyboardInterrupt exception 
            print('User interrupted the script. Exiting...') 
            sys.exit(0) # Exit with status code 0 (success)
        
        if answer == "yes":
            break
        if answer == "exit":
            sys.exit(0) # Exit with status code 0 (success)
        if answer == "change":
            while True:
                try:
                    args.u = int(input("Num: "))
                except ValueError: 
                    print("That was not a valid integer. Please try again.", file=sys.stderr)
                    continue
                except KeyboardInterrupt: 
                    # Handle the KeyboardInterrupt exception 
                    print('User interrupted the script. Exiting...') 
                    sys.exit(0) # Exit with status code 0 (success)
                break
        else:
            print("The answer should be (yes|exit|change) only.")
            continue

    # Check the arguments
    if args.r:
        if not args.t:
            with open('./fruits.list', 'r') as f:
                lines = f.readlines()
            random_line = secrets.choice(lines)
            string_variable = random_line.strip()
            
            try: 
                subprocess.run(["/bin/echo", f"{string_variable}"], check=True) 
            except subprocess.CalledProcessError as e: 
                print("Command failed with return code:", e.returncode, file=sys.stderr)
                sys.exit(1)

        else:
            print("-r and -t do not mix.", file=sys.stderr)
            sys.exit(2)
    if args.R:
        if not args.A:
            try: 
                subprocess.run(["/bin/echo", "Why do Python developers always write unit tests first? Because they don't want to get bit by a bug!"], check=True) 
            except subprocess.CalledProcessError as e: 
                print("Command failed with return code:", e.returncode, file=sys.stderr)
                sys.exit(1)
        else:
            print("-R and -A do not mix.", file=sys.stderr)
            sys.exit(2)              
    if args.s:
        try: 
            subprocess.run(["/bin/echo", "Why did the Python script get a job in testing? Because it was an expert in handling exceptions!"], check=True) 
        except subprocess.CalledProcessError as e: 
            print("Command failed with return code:", e.returncode, file=sys.stderr)
            sys.exit(1)
    if args.A:
        if not args.R:
            try: 
                subprocess.run(["/bin/echo", "Why did the Python script cross the road? To get to the other side of the integration test!"], check=True) 
            except subprocess.CalledProcessError as e: 
                print("Command failed with return code:", e.returncode, file=sys.stderr)
                sys.exit(1)
        else:
            print("-A and -R do not mix.", file=sys.stderr)
            sys.exit(2)                
    if args.l:
        try: 
            subprocess.run(["/bin/echo", "Why did the Python script fail the integration test? Because it couldn't handle the stress of working with other modules!"], check=True) 
        except subprocess.CalledProcessError as e: 
            print("Command failed with return code:", e.returncode, file=sys.stderr)
            sys.exit(1)
    if args.t:
        if not args.r:
            try: 
                subprocess.run(["/bin/echo", "Why did the Python script get lost in the testing environment? Because it forgot to import the os module!"], check=True) 
            except subprocess.CalledProcessError as e: 
                print("Command failed with return code:", e.returncode, file=sys.stderr)
                sys.exit(1)
        else:
            print("-t and -r do not mix.", file=sys.stderr)
            sys.exit(2)                
    if args.d:
        try: 
            subprocess.run(["/bin/echo", "Why did the Python script fail the performance test? Because it was too slow, like a python in hibernation!"], check=True) 
        except subprocess.CalledProcessError as e: 
            print("Command failed with return code:", e.returncode, file=sys.stderr)
            sys.exit(1)

    sys.exit(0)
            

I really don’t understand, isn’t ChatGPT created by humans? aren’t both humans (me) and ChatGPT not perfect?

So I wanted code review because I didn’t want to be ignorant about what my code does.

Nobody learns anything by being given the answer. Instead, … asks questions. Questions are thought-provoking. Questions are open-ended. And most importantly, questions lead to learning.

We, of course, don’t know what has made it necessary for you to complete this project in haste. However it is quite unfortunate that this has led to your relying on a large language model (LLM) as a technique for implementing it. Some of the consequences may be:

  • The result might be code that does not work properly.
  • Even if the code does come close to what you need, it may be difficult to understand, making it hard to adjust it to meet specifications.
  • Even if it does exactly what you wish it to do at the present time, code that is difficult to understand may be a challenge to refine later on in order to accommodate ideas that you may develop for innovations.

Is the code that ChatGPT produced understandable to you? If not, please consider what consequences this may have for the future.

If I give you feedback on code that ChatGPT generated, will it help ChatGPT to make better code in the future? No, because you are not in control of it, you do not affect the code it produces.

If I give you feedback on code that you created yourself, then yes, it can help you to make better code in the future, because you are in control of the code you create.

Very true. And nobody learns anything by having ChatGPT create code that might not even work. You DO learn by creating code yourself, and then finding out how well it works - by running it, and by asking other intelligent people to tell you about it.

It is a waste of our time to give you feedback on code that ChatGPT provided. Until you give us code that you, and you alone, created, I do not intend to spend time trying to point out ways the code can be improved. I’d much rather you show us code that is unfinished and/or doesn’t work, but which by its nature of having been created by you expresses your intent, than code which might fulfil some AI’s idea of what you might want, but is useless for review.

I can’t emphasize this strongly enough. ChatGPT is worse than useless for generating software. It is not a coding AI.

That is true. Khanmigo, to which you have devoted some attention, does indeed relate to how AI can aid in asking students questions within an educational setting. The topic of your original post is different, however, in that it is about using AI to generate or enhance answers rather than for it to ask questions for the learner to answer. To become fluent in programming, the learner needs to generate the answers.

The issue of whether or not to allow posting of responses generated by LLMs within this forum is currently under discussion. A number of participants in the discussion have expressed their concerns about LLMs, including hazards connected with using it to generate code. Obviously, some of these concerns can be applied to the discussion here. Please see Community policy on AI-generated answers, and consider what implications some of the commentary there has regarding your current task.