Man... Koitsu ninjaing me. Still posting.
or you could possibly open up the game in a debugging emulator and change different values in ram and see what happens.
The first step is usually finding RAM, which is pretty easy with a debugging emulator. The process is not changing values randomly, there's a method. You have a goal in mind.
A very common feature in debuggers allows you to narrow down RAM locations based on conditions. Say I'm playing a platformer, I want to find out which RAM value represents the player's X speed. If I stand still, it's probably gonna be zero.
So I eliminate every single piece of RAM that's not zero.
Then, I press right for a few frames to accelerate.
I now eliminate every single piece of RAM that either hasn't changed, or decreased.
Maybe there's only about three to four bytes of RAM it could be at this point. So I play in real time and watch to see which one corresponds to my movements.
Or... if there's still like sixty bytes of RAM, you let the player come to a stop completely. Then eliminate all RAM that isn't zero from your list.
Then press left and eliminate all RAM that either hasn't changed or increased.
(Just to note, the debugger keeps track of this RAM, and narrows it down. It's not like you've got a notepad file of what RAM it could be, and then have to verify each. You say, "Get rid of RAM that's not zero." And all RAM that's not zero is instantly gone from that list. You keep doing this until you've found what you're looking for.)
By now you've probably found X speed. Now, the above makes a few assumptions. It's not necessarily guaranteed that the RAM representing X speed is 0 when the player is standing still. And it's not necessarily true that the speed increases when you hold right. It could decrease (or do something weirder). But... you think what you'd do as a programmer and look for that.
After finding RAM, it's really easy to start to look into code. You can break on read and writes of the X speed variable, which will let you see how the game's acceleration works. Because the very code that is changing the variable is right there once the break happens! A good debugger will break the moment a write or read is detected, and show you the code around it. From there you could figure out what other RAM might represent in the context of code, again just thinking how you'd do it as a programmer.
As far as modifying the program, it's a lot like writing a regular program. The thing to understand is that every assembly instruction is represented by an "opcode". Which is a byte (or some bytes) that tells the CPU what to do. When an NES (or SNES) encounters $18, it clears the carry flag. If you were writing a program and typed CLC, it would become $18 once assembled. If you programmed for either console, you know what CLC does.
If you know how to program in the assembly language you're reading, you could look at the code the debugger broke on for X speed. (Which again, a good debugger would display as code and not hex. See what Koitsu posted from FCEUX's debugger.) You'd then figure out what changes you wanted to make, just like if it was your own program. And then you'd look up the opcodes for what you want to do and change what's there. (Or ignore opcodes entirely, and assemble a code snippet and paste the block there.)
The caveat with hacking (without a disassembly) is that you cannot simply add instructions in the middle of a routine that's already there, because it would break a bunch of branches and things. So you find some free space in the rom (depending on the rom, this can be tough) to add your new routine. And then you replace a few instructions in the area you want to add something to with a jump to that free space. You put your routine in the free space(with the instructions you just replaced with the jmp before the new routine so the program still executes them). At the end, you jump back. (Or just return.)
If you truly understand how to program with a given assembly language for a given system, hacking is not a lot of new knowledge. It can certainly be challenging... but probably not like mapping DNA. If I wanted to find out how other games optimized say... their graphics, I'd break on writes to the system in question's graphics registers.
Find how what's put there. Trace how THAT got there. Trace how THAT got there. Until I found it. Whee, crash course on executable hacking. You have the start with a goal, rather than, "I'm gonna read through this entire game line by line." Even if your goal is eventually making a disassembly, you break it up into smaller parts. First identifying what the RAM is used for, maybe.
tl;dr: Learning to program first is highly recommended. If hacking still seems difficult, keep practicing straight programming. I never learned to hack. One day, my friend asked me to make a small hack and I realized I totally could. I've never read anything on the subject.
A named edit (though I anticipate doing a few more to fix lots of typos: Knowing if something is hardcore optimized is again, just knowing the language. Sometimes you look at code in a game and just say, "Damn. This is REALLY clever."