{"id":289,"date":"2024-10-08T08:52:52","date_gmt":"2024-10-08T08:52:52","guid":{"rendered":"https:\/\/joskodeboer.nl\/?p=289"},"modified":"2026-04-10T14:45:00","modified_gmt":"2026-04-10T14:45:00","slug":"will-genai-replace-developers","status":"publish","type":"post","link":"https:\/\/joskodeboer.nl\/index.php\/2024\/10\/08\/will-genai-replace-developers\/","title":{"rendered":"Will genAI replace developers?"},"content":{"rendered":"<div class=\"et_pb_section_0 et_pb_section et_section_regular et_block_section preset--module--divi-section--default\"><div class=\"et_pb_row_0 et_pb_row et_block_row\"><div class=\"et_pb_column_0 et_pb_column et_pb_column_1_3 et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_image_0 et_pb_image et_pb_module et_block_module preset--module--divi-image--default\"><span class=\"et_pb_image_wrap\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/AI-vs-Dev.png\" width=\"1024\" height=\"1024\" srcset=\"https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/AI-vs-Dev.png 1024w, https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/AI-vs-Dev-980x980.png 980w, https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/AI-vs-Dev-480x480.png 480w\" sizes=\"(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw\" class=\"wp-image-319\" title=\"AI vs Dev\" alt=\"AI image of robot versus developer\" \/><\/span><\/div><\/div><div class=\"et_pb_column_1 et_pb_column et_pb_column_2_3 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_0 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>We've all heard the rumors that we - the developers - will be replaced by genAI soon. The layoffs in recent years have been concerning to say the least.<\/p>\n<p>In this blog post, I'll introduce you to cursor AI and draw my own conclusions regarding my imminent replacement by that which I help create.<\/p>\n<p>The challenge I gave myself is to build a boardgame with reinforcement learning with only prompting - I'm not allowed to code. <a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\">The result is on GitHub<\/a>.<\/p>\n<p>Note that this post isn't sponsored, it's just an interesting example of current developments with generative AI assisted workflow.<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><\/div><\/div><\/div><div class=\"et_pb_section_1 et_pb_section et_section_regular et_block_section preset--module--divi-section--default\"><div class=\"et_pb_row_1 et_pb_row et_block_row\"><div class=\"et_pb_column_2 et_pb_column et_pb_column_4_4 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_1 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>What is Cursor AI?<\/h2>\n<\/div><\/div><div class=\"et_pb_text_2 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Cursor is a code editor, but with LLM enhancements. First, it enhances the common suggestion\/autocomplete feature into multiple edits at once. When you start coding, it sees what you did and does exactly what LLMs are made for: It predicts what you'll code next.<\/p>\n<p>It allows you to type carelessly; it autofixes typos, drawing from both the language constructs and your own context (e.g. variable names).\u00a0<\/p>\n<p>A really cool feature is that you can use\u00a0<em>CTRL+L<\/em> to open a chat with the LLM. It will have the context of your current file, and you can ask it questions about the code. This can go from \"Can you explain what the current module does\" to \"Can you spot any bugs in this file\"?\u00a0<\/p>\n<p>In any interaction with the LLM, you can use the <a href=\"https:\/\/docs.cursor.com\/context\/@-symbols\/basic\">@ symbol<\/a> to add context - other code files. So, you might be able to ask more complicated questions such as \"When we take this file together with @src\/logic\/utilities.py, what can we do?\". The chat suggests changes to your code that can be applied immediately.<\/p>\n<p>Inside your code, you can open a smaller chat window and give direct instructions. You can select code or just \"cursor\" in your file, and tell it how the code should be changed. The same window can also be turned into a \"quick question\", so that you don't need to open the full chat.<\/p>\n<p><a href=\"https:\/\/www.cursor.com\/features\">See more on Cursor's features.<\/a><\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><div class=\"et_pb_text_3 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>My test case: The boardgame cryptid<\/h2>\n<\/div><\/div><div class=\"et_pb_text_4 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>My test case is a boardgame?<\/p>\n<p>Yes! Let me explain my reasoning. First off, I wanted something with a certain degree of complexity to really put Cursor through it's paces. After all, creating API endpoints for different tables is something we can automate without LLMs, but creating a fairly complex boardgame, using smart algorithms to find different answers and using reinforcement learning to create automated bots for the game should offer a far greater challenge!<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><\/div><\/div><div class=\"et_pb_row_2 et_pb_row et_block_row\"><div class=\"et_pb_column_3 et_pb_column et_pb_column_2_5 et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_image_1 et_pb_image et_pb_module et_block_module preset--module--divi-image--default\"><span class=\"et_pb_image_wrap\"><img decoding=\"async\" src=\"https:\/\/www.spellenrijk.nl\/resize\/cryptid-overview_10645014469644.jpg\/500\/500\/True\/cryptid-board-game-2.webp\" \/><\/span><\/div><\/div><div class=\"et_pb_column_4 et_pb_column et_pb_column_3_5 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_5 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Cryptid is a boardgame in which three to five players try to find \"the cryptid\". To this end, you first pick a random scenario card. The card specifies how to put down the six parts of the map, and which player receives which \"hint\". A hint is something like \"The cryptid can be found either on water or in a forest\". Throughout the game, you are questioning the other players, trying to determine what each of their hints are so that you can be the first to find the cryptid.<\/p>\n<\/div><\/div><\/div><\/div><div class=\"et_pb_row_3 et_pb_row et_block_row\"><div class=\"et_pb_column_5 et_pb_column et_pb_column_4_4 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_6 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>There's a few features to the map. Each tile has a terrain type: Forest, Swamp, Desert, Mountain and Water. A tile can contain a structure - either a shack or a standing stone - and that structure has a color (blue, green, white, black). There are also some areas of 2-3 connected tiles that contain an animal territory, indicated by a dashed border. The animals are bears and cougars.<\/p>\n<p>During each of your turns, you either guess at a location - in which case, each player in order will tell whether or not you are correct - or question another player to see if a certain location is allowed by their hint.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><div class=\"et_pb_text_7 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h3>Reinforcement learning<\/h3>\n<\/div><\/div><div class=\"et_pb_text_8 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Reinforcement learning is a Machine Learning algorithm in which an agent will \"Learn\" how to properly navigate a problem by exploring and taking that feedback, building \"knowledge\" about the problem and using that to find an optimal course.<\/p>\n<p>I won't go into too much detail, but we'll be trying to use \"<em>Episodal\u00a0 Q-learning\"<\/em>. The name of this technique is about the way we acquire knowledge, which is represented by the \"Q matrix\" and the algorithm is about the rules of putting the knowledge in. In our problem the algorithm is rewarded for winning the game (a game is an episode) and punished for game length and losing the game.\u00a0<\/p>\n<p>We'll also have a policy, a way of using knowledge to pick our move, that has a random component. In this case, either explore or pick one of the\u00a0<em>top ten<\/em> moves. Not picking \"the best\" move allows tuning our future bots to different levels (easy, hard), if we ever want to use them like that.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><div class=\"et_pb_text_9 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>Generating basic functionality<\/h2>\n<\/div><\/div><div class=\"et_pb_text_10 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>The first feature I wanted to look into was Cursor using a library of sorts. I know that I'm going to use logic around a hexagonal grid, so I can generate some basic functions around that. Some of the functions gave me some trouble - it would generate deeply nested loops. I found that very specific instructions were required, like \"write a function that does X\" and then \"write a function that does Y using the previous function for X\".<\/p>\n<p>The game wants you to find objects within distance X. So, a useful function would be that when you have an attribute on a node, you can spread \"my neighbor has this\" to adjacent\/connected nodes. In this case, I want to be able to spread the attribute\u00a0<em>forest=True<\/em> to each neighbor. The node itself and its neighbors shall have\u00a0<em>neighbor_forest=True<\/em> and their neighbors will have\u00a0<em>neighbor_neighbor_forest=True<\/em>, etc. But I don't want to nest a bunch of functions, so just first update neighbors and then see what nodes have neighbors with <em>neighbor_forest<\/em>, right?<\/p>\n<p>It worked out pretty well (<a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/utils\/graph_utils.py\">graph_utils.py<\/a>):<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><div class=\"et_pb_code_0 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><pre class=\"prettyprint\">\ndef update_neighbors_with_prefix(\n    graph: nx.Graph, attr: str, prefix: str, levels: int = 3\n) -> None:\n    \"\"\"\n    Update nodes with prefixed attributes based on the presence of the attribute in neighboring nodes.\n\n    Parameters:\n    - graph: A networkx graph (nx.Graph)\n    - attr: The attribute to check for\n    - prefix: The prefix for the new attribute\n    - levels: The number of levels to check (default is 3)\n    \"\"\"\n    for level in range(1, levels + 1):\n        current_neighbor = \"_\".join([prefix] * (level - 1))\n        current_attr = f\"{current_neighbor}_{attr}\" if level > 1 else attr\n        new_attr = f\"{'_'.join([prefix] * level)}_{attr}\"\n        updates = {}\n        for node in graph.nodes:\n            # Check if the current node or any of its neighbors have the attribute set to True\n            has_attr = graph.nodes[node].get(current_attr, False) or any(\n                graph.nodes[neighbor].get(current_attr, False)\n                for neighbor in graph.neighbors(node)\n            )\n            updates[node] = {new_attr: has_attr}\n\n        nx.set_node_attributes(graph, updates)\n\n\ndef enrich_node_attributes(graph: nx.Graph) -> nx.Graph:\n    \"\"\"\n    Enriches the graph by adding attributes to each node indicating whether\n    the node or its neighbors (up to 3 levels) have a certain boolean attribute set to True.\n\n    Parameters:\n    - graph: A networkx graph (nx.Graph) with boolean attributes on nodes (one-hot encoded).\n\n    Returns:\n    - The enriched networkx graph with additional attributes.\n    \"\"\"\n    # Identify boolean attributes\n    boolean_attributes = set()\n    for node, attrs in graph.nodes(data=True):\n        boolean_attributes.update(\n            attr for attr, value in attrs.items() if isinstance(value, bool)\n        )\n\n    # Update attributes for all levels\n    for attr in boolean_attributes:\n        update_neighbors_with_prefix(graph, attr, \"neighbor\", levels=3)\n\n    return graph\n    <\/pre><\/div><\/div><div class=\"et_pb_text_11 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>Basic tests<\/h2>\n<\/div><\/div><div class=\"et_pb_text_12 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>So now we want to create a test suite for the same functions we generated. I simply created a new file, opened up ctrl+k for inline edits and used the prompt \"Use @graph_utils.py and generate unit tests for each function in it\", generating the unit tests for me.<\/p>\n<p>For example, we tested the upper function (<a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/tests\/test_graph_utils.py\">test_graph_utils.py<\/a>):<\/p>\n<\/div><\/div><div class=\"et_pb_code_1 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><pre class=\"prettyprint\">\n  def test_enrich_node_attributes(self):\n        enriched_graph = enrich_node_attributes(self.graph)\n        for node in enriched_graph.nodes:\n            attrs = enriched_graph.nodes[node]\n            self.assertTrue(\n                any(\n                    attrs[f\"neighbor_is_{terrain}\"]\n                    for terrain in [\"swamp\", \"forest\", \"water\", \"mountain\", \"desert\"]\n                )\n            )\n          <\/pre><\/div><\/div><div class=\"et_pb_text_13 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>What I found is that the testing isn't exhaustive, and if you want to add more test cases and particularly edge cases you will need to be explicit.<\/p>\n<\/div><\/div><div class=\"et_pb_text_14 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>The Game<\/h2>\n<\/div><\/div><div class=\"et_pb_text_15 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Generating random terrain types and structures for different nodes was easy enough. The first really interesting function was creating animal territories. These need to be connected territories of 2-3 tiles. Cursor solved it readily (<a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/utils\/graph_generate_random_area.py\">graph_generate_random_area.py<\/a>):<\/p>\n<\/div><\/div><div class=\"et_pb_code_2 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><pre class=\"prettyprint\">\ndef add_connected_area_attribute(G: nx.Graph, attribute: str, N: int) -> None:\n    \"\"\"\n    Orchestrates the process of adding an attribute to a random hexagon node and generating\n    a connected area of N nodes with the given attribute set to True.\n\n    Parameters:\n    - G: The networkx graph (nx.Graph) representing the hexagonal grid.\n    - attribute: The name of the attribute to be added.\n    - N: The size of the connected area to be created.\n    \"\"\"\n    if N > len(G.nodes):\n        raise ValueError(\n            \"N cannot be greater than the total number of nodes in the graph.\"\n        )\n\n    initialize_node_attributes(G, attribute)\n    start_node = select_random_start_node(G)\n    connected_area = expand_connected_area(G, start_node, N)\n    assign_attribute_to_nodes(G, connected_area, attribute)\n    def select_random_start_node(G: nx.Graph) -> int:\n    \"\"\"\n    Select a random starting node from the graph.\n\n    Parameters:\n    - G: The networkx graph (nx.Graph).\n\n    Returns:\n    - A randomly selected node.\n    \"\"\"\n    return random.choice(list(G.nodes))\n\n\ndef expand_connected_area(G: nx.Graph, start_node: int, N: int) -> set:\n    \"\"\"\n    Expand from the start node to create a connected area of N nodes.\n\n    Parameters:\n    - G: The networkx graph (nx.Graph).\n    - start_node: The node from which to start the expansion.\n    - N: The desired size of the connected area.\n\n    Returns:\n    - A set of nodes that form the connected area.\n    \"\"\"\n    visited = set()\n    queue = [start_node]\n\n    while queue and len(visited) < N:\n        current_node = queue.pop(0)  # BFS: FIFO\n        if current_node not in visited:\n            visited.add(current_node)\n            neighbors = list(G.neighbors(current_node))\n            random.shuffle(neighbors)  # Shuffle to ensure randomness\n            queue.extend(neighbors)\n\n    return visited\n<\/pre><\/div><\/div><div class=\"et_pb_text_16 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>With the map now in place, I figured I was ready to create a \"solver\" - having perfect knowledge of each player's hint, where is the solution?<\/p>\n<p>The way the hint structure was setup, each player has a tuple with attributes that should be true on the node. It was straightforward to solve (<a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/cryptid\/game_rules.py#L243\">game_rules.py<\/a>):<\/p>\n<\/div><\/div><div class=\"et_pb_code_3 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><pre class=\"prettyprint\">\ndef hint_applies(G, node, hint):\n    for attribute in hint:\n        if G.nodes[node].get(attribute, False):\n            return True\n    return False\n\n\ndef count_tiles_fitting_hints(G, hints):\n    count = 0\n    fitting_nodes = []\n    for node in G.nodes():\n        if all(hint_applies(G, node, hint) for hint in hints):\n            count += 1\n            fitting_nodes.append(node)\n    return count, fitting_nodes\n\n<\/pre><\/div><\/div><\/div><\/div><div class=\"et_pb_row_4 et_pb_row et_block_row\"><div class=\"et_pb_column_6 et_pb_column et_pb_column_3_5 et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_image_2 et_pb_image et_pb_module et_block_module preset--module--divi-image--default\"><span class=\"et_pb_image_wrap\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/test_reinforcement.png\" width=\"3403\" height=\"4920\" srcset=\"https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/test_reinforcement.png 3403w, https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/test_reinforcement-1280x1851.png 1280w, https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/test_reinforcement-980x1417.png 980w, https:\/\/joskodeboer.nl\/wp-content\/uploads\/2024\/10\/test_reinforcement-480x694.png 480w\" sizes=\"(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) and (max-width: 1280px) 1280px, (min-width: 1281px) 3403px, 100vw\" class=\"wp-image-338\" title=\"test_reinforcement\" alt=\"Image testing various things with cryptid\" \/><\/span><\/div><\/div><div class=\"et_pb_column_7 et_pb_column et_pb_column_2_5 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_17 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>But something didn't quite check out. I started evaluation by hand and quickly found that the proposed solutions were wrong.\u00a0<\/p>\n<p>I instructed cursor to visualize the map, and add some extra elements. First, I had it place game tiles to visualize the adjacency logic. Around a certain color structure, if <em>neighbor_structure<\/em> is true, add all game pieces. For <em>neighbor_neighbor_structure<\/em>, add all but one, etc.<\/p>\n<p>The pattern originally laid out didn't make sense. So I prompted to pick some random nodes and draw a line to their neighbors - aha! One of the basic functions, <em>generate_hexagonal_grid<\/em>, had the adjacency wrong! It took some specific prompting to get it right. Now, at the start of every episode, I generate the image to the left so that I can sanity check the map.\u00a0<\/p>\n<\/div><\/div><\/div><\/div><div class=\"et_pb_row_5 et_pb_row et_block_row\"><div class=\"et_pb_column_8 et_pb_column et_pb_column_4_4 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_18 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>I'm not going to run you through all the code, but here's a short list of highlights:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/cryptid\/plotting.py#L6\">plotting with patches<\/a>: Cursor quickly learned to generate functions that created different patches for the visualization part.<\/li>\n<li><a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/b986c8d3e9f760c2881a2cb9f7f0dd6e0e1fd97e\/container\/cryptid\/board.py#L117\">board.py<\/a>: The generate game map function looks nice and clean.<\/li>\n<li><a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/cryptid\/game_rules.py#L431\">game_rules.py<\/a>: Implementing episodal Q learning was a breeze<\/li>\n<li><a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/utils\/graph_utils.py#L40\">graph_utils.py<\/a>: Initially I thought to create unique codes for each map state, but this wouldn't work for a game like this. Regardless, it was a problem solved readily.<\/li>\n<li><a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/blob\/main\/container\/cryptid\/game_rules.py#L414\">game_rules.py<\/a>: The running code was quite slow, but didn't ask a lot of my CPU. I instructed Cursor to refactor some things into being parallel, and it readily did so.<\/li>\n<\/ul>\n<\/div><\/div><\/div><\/div><div class=\"et_pb_row_6 et_pb_row et_block_row\"><div class=\"et_pb_column_9 et_pb_column et_pb_column_3_5 et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_19 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>It wasn't all easy, though. I ran into several problems, some more specific to Cursor and some more to using LLMs.<\/p>\n<p>One that I think is specific to Cursor: I kept having <a href=\"https:\/\/github.com\/daimonie\/cryptid-cursor\/commit\/b4effa9f35e267854d3caa2b046cfe004e678e51\">random duplicate functions<\/a> in my code. I didn't really figure out what caused it, but my surmise is that when I closed Cursor without accepting changes, it would keep the old and the new but lost the context. After I rigorously started accepting changes before closing, I did not notice the problem again. I also started regularly using the LLM chat (CTRL+L) to tell me whether there were duplicate functions in the code.<\/p>\n<p>The more general one is <strong>hallucinations<\/strong>. Even with my code base indexed and sometimes provided specific instructions to use a certain file, the LLM would regularly start imagining things. For example, at several times it imagined\u00a0<em>is_legal_move<\/em> being a function in\u00a0<em>game_rules<\/em>\u00a0 but it just wasn't there. Usually, resetting the LLM context by restarting Cursor did wonders for its hallucinations. But keep in mind they do happen, and that sometimes it will also just create code that can't fail but is completely useless. Evaluate what it did rigorously before you accept the change. Zero hallucinations is possible (<a href=\"https:\/\/www.youtube.com\/watch?v=Rauz-3jycQ0\">a fun story around this<\/a>).<\/p>\n<\/div><\/div><\/div><div class=\"et_pb_column_10 et_pb_column et_pb_column_2_5 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_blurb_0 et_pb_blurb et_pb_bg_layout_light et_pb_blurb_position_left et_pb_module et_block_module preset--module--divi-blurb--default\"><div class=\"et_pb_blurb_content\"><div class=\"et_pb_main_blurb_image\"><span class=\"et_pb_image_wrap et_pb_only_image_mode_wrap\"><img decoding=\"async\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA4MCIgaGVpZ2h0PSI1NDAiIHZpZXdCb3g9IjAgMCAxMDgwIDU0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZmlsbD0iI0VCRUJFQiIgZD0iTTAgMGgxMDgwdjU0MEgweiIvPgogICAgICAgIDxwYXRoIGQ9Ik00NDUuNjQ5IDU0MGgtOTguOTk1TDE0NC42NDkgMzM3Ljk5NSAwIDQ4Mi42NDR2LTk4Ljk5NWwxMTYuMzY1LTExNi4zNjVjMTUuNjItMTUuNjIgNDAuOTQ3LTE1LjYyIDU2LjU2OCAwTDQ0NS42NSA1NDB6IiBmaWxsLW9wYWNpdHk9Ii4xIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgICAgICA8Y2lyY2xlIGZpbGwtb3BhY2l0eT0iLjA1IiBmaWxsPSIjMDAwIiBjeD0iMzMxIiBjeT0iMTQ4IiByPSI3MCIvPgogICAgICAgIDxwYXRoIGQ9Ik0xMDgwIDM3OXYxMTMuMTM3TDcyOC4xNjIgMTQwLjMgMzI4LjQ2MiA1NDBIMjE1LjMyNEw2OTkuODc4IDU1LjQ0NmMxNS42Mi0xNS42MiA0MC45NDgtMTUuNjIgNTYuNTY4IDBMMTA4MCAzNzl6IiBmaWxsLW9wYWNpdHk9Ii4yIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz4KICAgIDwvZz4KPC9zdmc+Cg==\" class=\"et_animated\" \/><\/span><\/div><div class=\"et_pb_blurb_container\"><h4 class=\"et_pb_module_header\">Coding style comment<\/h4><div class=\"et_pb_blurb_description\"><p>It's very helpful to separate code into\u00a0<em>orchestrators<\/em> and\u00a0<em> execution <\/em>functions. An orchestrator is a recipe; it calls several execution functions, it might loop over a variable and call the execution function, but it doesn't contain any true logic itself.<\/p>\n<p>This makes it fairly straightforward to unit test and keeps the code looking clean.<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><\/div><\/div><\/div><\/div><div class=\"et_pb_row_7 et_pb_row et_block_row\"><div class=\"et_pb_column_11 et_pb_column et_pb_column_4_4 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\"><div class=\"et_pb_text_20 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>Takeaways<\/h2>\n<\/div><\/div><div class=\"et_pb_text_21 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Using an LLM code editor was fun. The requirement that I was only allowed to prompt sometimes frustrated me, especially when I saw exactly what needed to happen but had to engineer a specific prompt to get it to happen.<\/p>\n<p>Another point is that I often had to prompt Cursor to refactor the code it generated. It might be because it trained on StackOverflow, but a lot of the code it wrote wasn't tidy, was large blocks of spaghetti, or wasn't DRY. A common prompt would be<em> \"Write a function do_something(x) that does something, then write a function orchestrate(y) that calls do_something(x) for each x in y\". <\/em>It works, and over time I usually included this kind of instruction to ensure I got cleaner code. (We don't talk about reinforcement_learning.py).<\/p>\n<p>Some things are straightforward problems for Cursor to tackle.\u00a0 <em>\"For this list of tables, create an API to read data\"<\/em>. That is one of the larger categories of code problems to solve and Cursor excels at it. By working on a specific boardgame with specific rules, I was able to put Cursor to the test a bit more. It worked out, but only with prompt engineering and practise.<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><div class=\"et_pb_text_22 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><h2>Conclusions<\/h2>\n<\/div><\/div><div class=\"et_pb_text_23 et_pb_text et_pb_bg_layout_light et_pb_module et_block_module preset--module--divi-text--default\"><div class=\"et_pb_text_inner\"><p>Interacting with this LLM makes me think of training a new person. I'm giving specific instructions, because they don't know what's expected. I'm carefully evaluating the results for the same reason, often double checking the logic and making sure there are no weird additions.\u00a0<\/p>\n<p>When you train a new person, you don't expect 200% productivity immediately. Rather, you expect the senior to have less productivity and the result to be 100-150% productivity for both of them together, but with (eventual) results around 200% or more.<\/p>\n<p>LLM-enhanced coding feels like that. I'm more efficient, but I am differently engaged. Slowly, the LLM and I get used to each other and we're working together better. My productivity is enhanced.<\/p>\n<p>It only works because I'm a more senior developer who can write these specific prompts. LLMs replace juniors because they're more productive. How would we get new seniors in that world? What does the new growth path from a self-taught kid on the internet or new graduate to the senior developer look like? We need to consider questions like that, or we will eventually find ourself without new developers.<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[9,10],"tags":[],"class_list":["post-289","post","type-post","status-publish","format-standard","hentry","category-software-engineering","category-tech"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/posts\/289","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/comments?post=289"}],"version-history":[{"count":17,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/posts\/289\/revisions"}],"predecessor-version":[{"id":555,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/posts\/289\/revisions\/555"}],"wp:attachment":[{"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/media?parent=289"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/categories?post=289"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joskodeboer.nl\/index.php\/wp-json\/wp\/v2\/tags?post=289"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}