Andrew Harry

andrewharry.com

Helping AI write

Better Code

How to Get Bing Chat to Write Better Code

It seems overnight a whole new industry was born - the prompt engineers! I’m sure most of us in the programming space have by now dabbled a little into the new AI waters.

A year ago I would have poured scorn on the idea that I can be replaced by some fancy autocomplete bot.
I certainly wouldn’t have guessed that I would be writing about using AI to write code.

frankly, this makes my job easier

But I believe that it is worth embracing these new developments. Because, frankly, it makes my job easier. I welcome being more productive in my day job. Being productive is one of the things that make me feel good.

What do you get out of the box?

Without much effort, you can get some decent code out of Bing Chat. But it is quite literal. You need to be very precise with what you ask for.

In this blog post, I am going to work on a ‘simple-ish’ program which builds a dependency tree for a given class using c#.

What are my requirements?

  • Write a program in C# which creates a dependency tree structure for a given class.
  • It needs to evaluate the constructor for a given class.
  • It should only use the constructor with the largest number of parameters (dependencies).
  • It should only capture dependencies which are classes or interfaces and should exclude strings.
  • It should build a tree-like structure which is returned at the end of the function.

An interesting side effect of creating the prompt…

You really need to think about what you want!

Version 1.0

Write a program in C# which creates a dependency tree structure for a given class. 
It needs to evaluate the constructor for a given class. 
It should only use the constructor with the largest number of parameters (dependencies). 
It should only capture dependencies which are classes or interfaces and should exclude strings. 
It should build a tree-like structure which is returned at the end of the function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class DependencyTree
{
    public Type Type { get; set; }
    public List<DependencyTree> Dependencies { get; set; }

    public DependencyTree(Type type)
    {
        Type = type;
        Dependencies = new List<DependencyTree>();
    }

    public static DependencyTree BuildTree(Type rootType)
    {
        var tree = new DependencyTree(rootType);
        var constructors = rootType.GetConstructors();
        if (constructors.Length > 0)
        {
            var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).First();
            foreach (var parameter in constructor.GetParameters())
            {
                if (parameter.ParameterType.IsClass || parameter.ParameterType.IsInterface)
                {
                    tree.Dependencies.Add(BuildTree(parameter.ParameterType));
                }
            }
        }
        return tree;
    }
}

This is actually pretty good and meets all of my requirements. Interesting I never actually asked it to use recursion which it has used as part of it’s solution.

I am going to run the prompt a second time and see if I get different results…

I’m sorry, but I couldn’t find any specific information on how to create a dependency tree structure for a given class in C# that meets all the requirements you mentioned. However, you can create dependency diagrams for Visual C# projects in Visual Studio to visualize your software system’s high-level, logical architecture Is there anything else I can help you with?

Strangely, it failed the second time around. Weird. Once I cleared the session aka ‘New Topic’ it worked again. This time the code had all of the logic in the constructor, which is not great.

Can we make it better?

Some obvious issues that I can see with the generated code:

  • There is lots of logic which should be extracted into well named private methods.
  • The readability could be improved by reducing nested logic.

Lets see if we can get bing to improve the code…

Version 1.1

Can you improve the readability of the above code by moving internal logic into well named private methods
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class DependencyTree
{
    public Type Type { get; set; }
    public List<DependencyTree> Dependencies { get; set; }

    public DependencyTree(Type type)
    {
        Type = type;
        Dependencies = new List<DependencyTree>();
    }

    public static DependencyTree BuildTree(Type rootType)
    {
        var tree = new DependencyTree(rootType);
        var constructors = rootType.GetConstructors();
        if (constructors.Length > 0)
        {
            var constructor = GetConstructorWithMostParameters(constructors);
            foreach (var parameter in constructor.GetParameters())
            {
                if (IsClassOrInterface(parameter.ParameterType))
                {
                    tree.Dependencies.Add(BuildTree(parameter.ParameterType));
                }
            }
        }
        return tree;
    }

    private static ConstructorInfo GetConstructorWithMostParameters(ConstructorInfo[] constructors)
    {
        return constructors.OrderByDescending(c => c.GetParameters().Length).First();
    }

    private static bool IsClassOrInterface(Type type)
    {
        return type.IsClass || type.IsInterface;
    }
}

Cool, I think it understood what I wanted. Lets see if we can get the code to have early exits!

Version 1.2

Can you improve the readability of the above code by reducing the nested logic
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class DependencyTree
{
    public Type Type { get; set; }
    public List<DependencyTree> Dependencies { get; set; }

    public DependencyTree(Type type)
    {
        Type = type;
        Dependencies = new List<DependencyTree>();
    }

    public static DependencyTree BuildTree(Type rootType)
    {
        var tree = new DependencyTree(rootType);
        var constructors = rootType.GetConstructors();

        if (constructors.Length == 0) 
          return tree;

        var constructor = GetConstructorWithMostParameters(constructors);
        foreach (var parameter in constructor.GetParameters())
        {
            if (!IsClassOrInterface(parameter.ParameterType)) 
              continue;
            tree.Dependencies.Add(BuildTree(parameter.ParameterType));
        }
        return tree;
    }

    private static ConstructorInfo GetConstructorWithMostParameters(ConstructorInfo[] constructors)
    {
        return constructors.OrderByDescending(c => c.GetParameters().Length).First();
    }

    private static bool IsClassOrInterface(Type type)
    {
        return type.IsClass || type.IsInterface;
    }
}

That’s Amazing

I am amazed that it can understand subtle concepts like ‘reduced nesting’ and ‘well named’ methods. BUT it doesn’t do this out of the box. How do we incorporate these requirements from the start? Also, can we drop the explanation and ‘chat’ part of the process?

How do we incorporate these requirements from the start?

Version 2.0

Write a program in C# which creates a dependency tree structure for a given class. 
Don't provide any preamble text saying "Here is a program..."
It needs to evaluate the constructor for a given class.
It should only use the constructor with the largest number of parameters (dependencies).
It should only capture dependencies which are classes or interfaces and should exclude strings.
It should build a tree-like structure which is returned at the end of the function.
It should have well named private methods for internal logic.
It should use early exits and other patterns to avoid deeply nested logic.
Please do not restate any part of this request in your response.
Don't explain how the code works.
Don't follow up with any suggested questions. 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class DependencyTree
{
    public Type Type { get; }
    public List<DependencyTree> Dependencies { get; }

    public DependencyTree(Type type)
    {
        Type = type;
        Dependencies = new List<DependencyTree>();
    }
}

public class DependencyTreeBuilder
{
    public DependencyTree Build(Type type)
    {
        var tree = new DependencyTree(type);
        var constructors = GetConstructors(type);
        if (constructors.Length == 0) return tree;

        var constructor = constructors.OrderByDescending(c => c.GetParameters().Length).First();
        foreach (var parameter in constructor.GetParameters())
        {
            if (parameter.ParameterType == typeof(string)) continue;
            if (!parameter.ParameterType.IsClass && !parameter.ParameterType.IsInterface) continue;

            tree.Dependencies.Add(Build(parameter.ParameterType));
        }

        return tree;
    }

    private ConstructorInfo[] GetConstructors(Type type)
    {
        return type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
    }
}

It’s not bad, but I’m still not happy with the amount of logic still inside the class.

Lets see if a subsequent prompt clears this up.

Version 2.1

Can you improve the readability of the above code by moving more internal logic into well named private methods
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class DependencyTree
{
    public Type Type { get; }
    public List<DependencyTree> Dependencies { get; }

    public DependencyTree(Type type)
    {
        Type = type;
        Dependencies = new List<DependencyTree>();
    }
}

public class DependencyTreeBuilder
{
    public DependencyTree Build(Type type)
    {
        var tree = new DependencyTree(type);
        var constructor = GetConstructorWithMostParameters(type);
        if (constructor == null) return tree;

        foreach (var parameter in constructor.GetParameters())
        {
            if (IsExcludedType(parameter.ParameterType)) continue;
            tree.Dependencies.Add(Build(parameter.ParameterType));
        }

        return tree;
    }

    private ConstructorInfo GetConstructorWithMostParameters(Type type)
    {
        var constructors = GetConstructors(type);
        if (constructors.Length == 0) return null;

        return constructors.OrderByDescending(c => c.GetParameters().Length).First();
    }

    private ConstructorInfo[] GetConstructors(Type type)
    {
        return type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
    }

    private bool IsExcludedType(Type type)
    {
        return type == typeof(string) || (!type.IsClass && !type.IsInterface);
    }
}

This might be always a two step process to get a good acceptable outcome.

What to do with the prompts?

I haven’t fully got AI in my daily workflows yet. But I suspect that I will want to keep the original prompts around for future reference. Maybe it is a good idea to save the prompt in the top class summary?

I think the main take away here are the generic prompt hints.

Don't provide any preamble text saying "Here is a program..."
It should have well named private methods for internal logic.
It should use early exits and other patterns to avoid deeply nested logic.
Please do not restate any part of this request in your response.
Don't explain how the code works.
Don't follow up with any suggested questions. 

Coming to terms with our new AI co-pilots

AI is becoming prevalent in software development. It can assist programmers in writing new and better code. However, this also poses some challenges and opportunities for developers, who need to come to terms with their new AI co-pilots.

AI is not a magic solution that can solve any problem or write any code. It is still dependent on the data it is trained on, and it may make mistakes or produce unexpected results. Developers need to be aware of these potential pitfalls, and to verify and test the code generated or modified by AI.

Trust but verify!

Microsoft very carefully chose the name copilot and for good reason.

Contents